From a4175dff23621eeab6f20efdca5833171fdb90f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 08:52:36 -0700 Subject: [PATCH 1/5] [release/10.0-preview4] Tar: Adjust the way we write GNU longlink and longpath metadata (#114942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix 3 GNU longlink and longpath bugs The Tar entry types Longlink and Longpath, which are exclusive to the GNU format, are metadata entries used to store longer-than-expected link or name strings (respectively). They precede their actual entry, and they are not visible to the user, only the data they hold is visible. Most of the regular header metadata in this metadata entries is not relevant, but we need to make sure it is well formed so other tools can read them properly. There are 3 fields that were not getting their values stored as expected by other tools: - The mtime, ctime and atime fields need to be set to the Unix Epoch so it shows up as zeros (0x30) by default. We were storing them as null (0x0) characters instead. For consistency, if a regular entry happens to get its mtime set to the Epoch, it should also be shown as zeros (0x30). - The uid, gid, uname and gname fields were not being set. These fields aren't really important in a metadata entry, but other tools set this to the default value (uid=gid=0 and uname=gname=root). Based on the fact that 'a value' is preferred for these fields, we can set them to the same value that the real entry has (not root). - The long text value (either link or name) should be null terminated, and this extra character should be counted in the size of the longpath. The library libdpkg, used by the dpkg-deb tool in Debian, expects a null terminator in longlink and longpath data starting in Debian 12. Without the terminator, these entries are considered malformed. --------- Co-authored-by: Carlos Sánchez López <1175054+carlossanlop@users.noreply.github.com> --- .../src/System/Formats/Tar/TarHeader.Write.cs | 97 +- .../tests/TarEntry/GnuTarEntry.Tests.cs | 1457 ++++++++++------- .../tests/TarWriter/TarWriter.Tests.cs | 18 +- 3 files changed, 891 insertions(+), 681 deletions(-) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs index baffefad104b9c..dd7e15753cf0bb 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs @@ -354,6 +354,13 @@ internal async Task WriteAsPaxAsync(Stream archiveStream, Memory buffer, C await WriteWithSeekableDataStreamAsync(TarEntryFormat.Pax, archiveStream, buffer, cancellationToken).ConfigureAwait(false); } } + // Checks if the linkname string is too long to fit in the regular header field. + // .NET strings do not include a null terminator by default, need to add it manually and also consider it for the length. + private bool IsLinkNameTooLongForRegularField() => _linkName != null && (Encoding.UTF8.GetByteCount(_linkName) + 1) > FieldLengths.LinkName; + + // Checks if the name string is too long to fit in the regular header field. + // .NET strings do not include a null terminator by default, need to add it manually and also consider it for the length. + private bool IsNameTooLongForRegularField() => (Encoding.UTF8.GetByteCount(_name) + 1) > FieldLengths.Name; // Writes the current header as a Gnu entry into the archive stream. // Makes sure to add the preceding LongLink and/or LongPath entries if necessary, before the actual entry. @@ -361,19 +368,19 @@ internal void WriteAsGnu(Stream archiveStream, Span buffer) { Debug.Assert(archiveStream.CanSeek || _dataStream == null || _dataStream.CanSeek); - // First, we determine if we need a preceding LongLink, and write it if needed - if (_linkName != null && Encoding.UTF8.GetByteCount(_linkName) > FieldLengths.LinkName) + if (IsLinkNameTooLongForRegularField()) { - TarHeader longLinkHeader = GetGnuLongMetadataHeader(TarEntryType.LongLink, _linkName); + // Linkname is too long for the regular header field, create a longlink entry where the linkname will be stored. + TarHeader longLinkHeader = GetGnuLongLinkMetadataHeader(); Debug.Assert(longLinkHeader._dataStream != null && longLinkHeader._dataStream.CanSeek); // We generate the long metadata data stream, should always be seekable longLinkHeader.WriteWithSeekableDataStream(TarEntryFormat.Gnu, archiveStream, buffer); buffer.Clear(); // Reset it to reuse it } - // Second, we determine if we need a preceding LongPath, and write it if needed - if (Encoding.UTF8.GetByteCount(_name) > FieldLengths.Name) + if (IsNameTooLongForRegularField()) { - TarHeader longPathHeader = GetGnuLongMetadataHeader(TarEntryType.LongPath, _name); + // Name is too long for the regular header field, create a longpath entry where the name will be stored. + TarHeader longPathHeader = GetGnuLongPathMetadataHeader(); Debug.Assert(longPathHeader._dataStream != null && longPathHeader._dataStream.CanSeek); // We generate the long metadata data stream, should always be seekable longPathHeader.WriteWithSeekableDataStream(TarEntryFormat.Gnu, archiveStream, buffer); buffer.Clear(); // Reset it to reuse it @@ -397,19 +404,19 @@ internal async Task WriteAsGnuAsync(Stream archiveStream, Memory buffer, C Debug.Assert(archiveStream.CanSeek || _dataStream == null || _dataStream.CanSeek); cancellationToken.ThrowIfCancellationRequested(); - // First, we determine if we need a preceding LongLink, and write it if needed - if (_linkName != null && Encoding.UTF8.GetByteCount(_linkName) > FieldLengths.LinkName) + if (IsLinkNameTooLongForRegularField()) { - TarHeader longLinkHeader = GetGnuLongMetadataHeader(TarEntryType.LongLink, _linkName); + // Linkname is too long for the regular header field, create a longlink entry where the linkname will be stored. + TarHeader longLinkHeader = GetGnuLongLinkMetadataHeader(); Debug.Assert(longLinkHeader._dataStream != null && longLinkHeader._dataStream.CanSeek); // We generate the long metadata data stream, should always be seekable await longLinkHeader.WriteWithSeekableDataStreamAsync(TarEntryFormat.Gnu, archiveStream, buffer, cancellationToken).ConfigureAwait(false); buffer.Span.Clear(); // Reset it to reuse it } - // Second, we determine if we need a preceding LongPath, and write it if needed - if (Encoding.UTF8.GetByteCount(_name) > FieldLengths.Name) + if (IsNameTooLongForRegularField()) { - TarHeader longPathHeader = GetGnuLongMetadataHeader(TarEntryType.LongPath, _name); + // Name is too long for the regular header field, create a longpath entry where the name will be stored. + TarHeader longPathHeader = GetGnuLongPathMetadataHeader(); Debug.Assert(longPathHeader._dataStream != null && longPathHeader._dataStream.CanSeek); // We generate the long metadata data stream, should always be seekable await longPathHeader.WriteWithSeekableDataStreamAsync(TarEntryFormat.Gnu, archiveStream, buffer, cancellationToken).ConfigureAwait(false); buffer.Span.Clear(); // Reset it to reuse it @@ -426,8 +433,30 @@ internal async Task WriteAsGnuAsync(Stream archiveStream, Memory buffer, C } } + private static MemoryStream GetLongMetadataStream(string text) + { + MemoryStream data = new MemoryStream(); + data.Write(Encoding.UTF8.GetBytes(text)); + data.WriteByte(0); // Add a null terminator at the end of the string, _size will be calculated later + data.Position = 0; + return data; + } + + private TarHeader GetGnuLongLinkMetadataHeader() + { + Debug.Assert(_linkName != null); + MemoryStream dataStream = GetLongMetadataStream(_linkName); + return GetGnuLongMetadataHeader(dataStream, TarEntryType.LongLink, _uid, _gid, _uName, _gName); + } + + private TarHeader GetGnuLongPathMetadataHeader() + { + MemoryStream dataStream = GetLongMetadataStream(_name); + return GetGnuLongMetadataHeader(dataStream, TarEntryType.LongPath, _uid, _gid, _uName, _gName); + } + // Creates and returns a GNU long metadata header, with the specified long text written into its data stream (seekable). - private static TarHeader GetGnuLongMetadataHeader(TarEntryType entryType, string longText) + private static TarHeader GetGnuLongMetadataHeader(MemoryStream dataStream, TarEntryType entryType, int mainEntryUid, int mainEntryGid, string? mainEntryUname, string? mainEntryGname) { Debug.Assert(entryType is TarEntryType.LongPath or TarEntryType.LongLink); @@ -435,11 +464,15 @@ private static TarHeader GetGnuLongMetadataHeader(TarEntryType entryType, string { _name = GnuLongMetadataName, // Same name for both longpath or longlink _mode = TarHelpers.GetDefaultMode(entryType), - _uid = 0, - _gid = 0, - _mTime = DateTimeOffset.MinValue, // 0 + _uid = mainEntryUid, + _gid = mainEntryGid, + _mTime = DateTimeOffset.UnixEpoch, // 0 _typeFlag = entryType, - _dataStream = new MemoryStream(Encoding.UTF8.GetBytes(longText)) + _dataStream = dataStream, + _uName = mainEntryUname, + _gName = mainEntryGname, + _aTime = DateTimeOffset.UnixEpoch, // 0 + _cTime = DateTimeOffset.UnixEpoch, // 0 }; } @@ -614,17 +647,17 @@ private int WriteCommonFields(Span buffer, TarEntryType actualEntryType) int checksum = 0; - if (_mode > 0) + if (_mode >= 0) { checksum += FormatNumeric(_mode, buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)); } - if (_uid > 0) + if (_uid >= 0) { checksum += FormatNumeric(_uid, buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)); } - if (_gid > 0) + if (_gid >= 0) { checksum += FormatNumeric(_gid, buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)); } @@ -750,8 +783,8 @@ private int WritePosixAndGnuSharedFields(Span buffer) // Saves the gnu-specific fields into the specified spans. private int WriteGnuFields(Span buffer) { - int checksum = WriteAsGnuTimestamp(_aTime, buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); - checksum += WriteAsGnuTimestamp(_cTime, buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); + int checksum = WriteAsTimestamp(_aTime, buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); + checksum += WriteAsTimestamp(_cTime, buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); if (_gnuUnusedBytes != null) { @@ -1060,7 +1093,6 @@ private static int Checksum(ReadOnlySpan bytes) } return checksum; } - private int FormatNumeric(int value, Span destination) { Debug.Assert(destination.Length == 8, "8 byte field expected."); @@ -1118,6 +1150,7 @@ private static int FormatOctal(long value, Span destination) Span digits = stackalloc byte[32]; // longer than any possible octal formatting of a ulong int i = digits.Length - 1; + while (true) { digits[i] = (byte)('0' + (remaining % 8)); @@ -1136,24 +1169,6 @@ private int WriteAsTimestamp(DateTimeOffset timestamp, Span destination) return FormatNumeric(unixTimeSeconds, destination); } - // Writes the specified DateTimeOffset's Unix time seconds, and returns its checksum. - // If the timestamp is UnixEpoch, it writes 0s into the destination span. - private int WriteAsGnuTimestamp(DateTimeOffset timestamp, Span destination) - { - if (timestamp == DateTimeOffset.UnixEpoch) - { -#if DEBUG - for (int i = 0; i < destination.Length; i++) - { - Debug.Assert(destination[i] == 0, "Destination span should be zeroed."); - } -#endif - return 0; - } - - return WriteAsTimestamp(timestamp, destination); - } - // Writes the specified text as an UTF8 string aligned to the left, and returns its checksum. private static int WriteAsUtf8String(ReadOnlySpan text, Span buffer) { diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs index 4d9153ac57ce00..bc80d89becbc06 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs @@ -1,8 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; +using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; +using System.Numerics; using System.Text; using System.Threading.Tasks; using Xunit; @@ -10,742 +14,931 @@ namespace System.Formats.Tar.Tests { public class GnuTarEntry_Tests : TarTestsBase +{ + [Fact] + public void Constructor_InvalidEntryName() { - [Fact] - public void Constructor_InvalidEntryName() - { - Assert.Throws(() => new GnuTarEntry(TarEntryType.RegularFile, entryName: null)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.RegularFile, entryName: string.Empty)); - } + Assert.Throws(() => new GnuTarEntry(TarEntryType.RegularFile, entryName: null)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.RegularFile, entryName: string.Empty)); + } - [Fact] - public void Constructor_UnsupportedEntryTypes() - { - Assert.Throws(() => new GnuTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + [Fact] + public void Constructor_UnsupportedEntryTypes() + { + Assert.Throws(() => new GnuTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + + Assert.Throws(() => new GnuTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + + // These are specific to GNU, but currently the user cannot create them manually + Assert.Throws(() => new GnuTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + + // The user should not create these entries manually + Assert.Throws(() => new GnuTarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.LongPath, InitialEntryName)); + } - Assert.Throws(() => new GnuTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + [Fact] + public void SupportedEntryType_RegularFile() + { + GnuTarEntry regularFile = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + SetRegularFile(regularFile); + VerifyRegularFile(regularFile, isWritable: true); + } - // These are specific to GNU, but currently the user cannot create them manually - Assert.Throws(() => new GnuTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + [Fact] + public void SupportedEntryType_Directory() + { + GnuTarEntry directory = new GnuTarEntry(TarEntryType.Directory, InitialEntryName); + SetDirectory(directory); + VerifyDirectory(directory); + } - // The user should not create these entries manually - Assert.Throws(() => new GnuTarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.LongPath, InitialEntryName)); - } + [Fact] + public void SupportedEntryType_HardLink() + { + GnuTarEntry hardLink = new GnuTarEntry(TarEntryType.HardLink, InitialEntryName); + SetHardLink(hardLink); + VerifyHardLink(hardLink); + } - [Fact] - public void SupportedEntryType_RegularFile() + [Fact] + public void SupportedEntryType_SymbolicLink() + { + GnuTarEntry symbolicLink = new GnuTarEntry(TarEntryType.SymbolicLink, InitialEntryName); + SetSymbolicLink(symbolicLink); + VerifySymbolicLink(symbolicLink); + } + + [Fact] + public void SupportedEntryType_BlockDevice() + { + GnuTarEntry blockDevice = new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName); + SetBlockDevice(blockDevice); + VerifyBlockDevice(blockDevice); + } + + [Fact] + public void SupportedEntryType_CharacterDevice() + { + GnuTarEntry characterDevice = new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName); + SetCharacterDevice(characterDevice); + VerifyCharacterDevice(characterDevice); + } + + [Fact] + public void SupportedEntryType_Fifo() + { + GnuTarEntry fifo = new GnuTarEntry(TarEntryType.Fifo, InitialEntryName); + SetFifo(fifo); + VerifyFifo(fifo); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DataOffset_RegularFile(bool canSeek) + { + using MemoryStream ms = new(); + using (TarWriter writer = new(ms, leaveOpen: true)) { - GnuTarEntry regularFile = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - SetRegularFile(regularFile); - VerifyRegularFile(regularFile, isWritable: true); + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + entry.DataStream = new MemoryStream(); + entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); + entry.DataStream.Position = 0; + writer.WriteEntry(entry); } + ms.Position = 0; - [Fact] - public void SupportedEntryType_Directory() + using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + using TarReader reader = new(streamToRead); + TarEntry actualEntry = reader.GetNextEntry(); + Assert.NotNull(actualEntry); + + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 2560. + // The regular file data section starts on the next byte. + long expectedDataOffset = canSeek ? 512 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + + if (canSeek) { - GnuTarEntry directory = new GnuTarEntry(TarEntryType.Directory, InitialEntryName); - SetDirectory(directory); - VerifyDirectory(directory); + ms.Position = actualEntry.DataOffset; + byte actualData = (byte)ms.ReadByte(); + Assert.Equal(ExpectedOffsetDataSingleByte, actualData); } + } - [Fact] - public void SupportedEntryType_HardLink() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DataOffset_RegularFile_Async(bool canSeek) + { + await using MemoryStream ms = new(); + await using (TarWriter writer = new(ms, leaveOpen: true)) { - GnuTarEntry hardLink = new GnuTarEntry(TarEntryType.HardLink, InitialEntryName); - SetHardLink(hardLink); - VerifyHardLink(hardLink); + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + entry.DataStream = new MemoryStream(); + entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); + entry.DataStream.Position = 0; + await writer.WriteEntryAsync(entry); } + ms.Position = 0; - [Fact] - public void SupportedEntryType_SymbolicLink() + await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + await using TarReader reader = new(streamToRead); + TarEntry actualEntry = await reader.GetNextEntryAsync(); + Assert.NotNull(actualEntry); + + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 2560. + // The regular file data section starts on the next byte. + long expectedDataOffset = canSeek ? 512 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + + if (canSeek) { - GnuTarEntry symbolicLink = new GnuTarEntry(TarEntryType.SymbolicLink, InitialEntryName); - SetSymbolicLink(symbolicLink); - VerifySymbolicLink(symbolicLink); + ms.Position = actualEntry.DataOffset; + byte actualData = (byte)ms.ReadByte(); + Assert.Equal(ExpectedOffsetDataSingleByte, actualData); } + } - [Fact] - public void SupportedEntryType_BlockDevice() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DataOffset_RegularFile_LongPath(bool canSeek) + { + using MemoryStream ms = new(); + using (TarWriter writer = new(ms, leaveOpen: true)) { - GnuTarEntry blockDevice = new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName); - SetBlockDevice(blockDevice); - VerifyBlockDevice(blockDevice); + string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, veryLongName); + entry.DataStream = new MemoryStream(); + entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); + entry.DataStream.Position = 0; + writer.WriteEntry(entry); } + ms.Position = 0; + + using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + using TarReader reader = new(streamToRead); + TarEntry actualEntry = reader.GetNextEntry(); + Assert.NotNull(actualEntry); + + // GNU first writes the long path entry, containing: + // * 512 bytes of the regular tar header + // * 1234 bytes for the data section containing the full long path + // * 302 bytes of padding + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 2560. + // The regular file data section starts on the next byte. + long expectedDataOffset = canSeek ? 2560 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + } - [Fact] - public void SupportedEntryType_CharacterDevice() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DataOffset_RegularFile_LongPath_Async(bool canSeek) + { + await using MemoryStream ms = new(); + await using (TarWriter writer = new(ms, leaveOpen: true)) { - GnuTarEntry characterDevice = new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName); - SetCharacterDevice(characterDevice); - VerifyCharacterDevice(characterDevice); + string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, veryLongName); + entry.DataStream = new MemoryStream(); + entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); + entry.DataStream.Position = 0; + await writer.WriteEntryAsync(entry); } + ms.Position = 0; + + await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + await using TarReader reader = new(streamToRead); + TarEntry actualEntry = await reader.GetNextEntryAsync(); + Assert.NotNull(actualEntry); + + // GNU first writes the long path entry, containing: + // * 512 bytes of the regular tar header + // * 1234 bytes for the data section containing the full long path + // * 302 bytes of padding + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 2560. + // The regular file data section starts on the next byte. + long expectedDataOffset = canSeek ? 2560 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + } - [Fact] - public void SupportedEntryType_Fifo() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DataOffset_RegularFile_LongLink(bool canSeek) + { + using MemoryStream ms = new(); + using (TarWriter writer = new(ms, leaveOpen: true)) { - GnuTarEntry fifo = new GnuTarEntry(TarEntryType.Fifo, InitialEntryName); - SetFifo(fifo); - VerifyFifo(fifo); + GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, InitialEntryName); + entry.LinkName = new string('a', 1234); // Forces using a GNU LongLink entry + writer.WriteEntry(entry); } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void DataOffset_RegularFile(bool canSeek) + ms.Position = 0; + + using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + using TarReader reader = new(streamToRead); + TarEntry actualEntry = reader.GetNextEntry(); + Assert.NotNull(actualEntry); + + // GNU first writes the long link entry, containing: + // * 512 bytes of the regular tar header + // * 1234 bytes for the data section containing the full long link + // * 302 bytes of padding + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 2560. + // The regular file data section starts on the next byte. + long expectedDataOffset = canSeek ? 2560 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DataOffset_RegularFile_LongLink_Async(bool canSeek) + { + await using MemoryStream ms = new(); + await using (TarWriter writer = new(ms, leaveOpen: true)) { - using MemoryStream ms = new(); - using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - entry.DataStream = new MemoryStream(); - entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); - entry.DataStream.Position = 0; - writer.WriteEntry(entry); - } - ms.Position = 0; - - using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - using TarReader reader = new(streamToRead); - TarEntry actualEntry = reader.GetNextEntry(); - Assert.NotNull(actualEntry); - - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 2560. - // The regular file data section starts on the next byte. - long expectedDataOffset = canSeek ? 512 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); - - if (canSeek) - { - ms.Position = actualEntry.DataOffset; - byte actualData = (byte)ms.ReadByte(); - Assert.Equal(ExpectedOffsetDataSingleByte, actualData); - } + GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, InitialEntryName); + entry.LinkName = new string('b', 1234); // Forces using a GNU LongLink entry + await writer.WriteEntryAsync(entry); } + ms.Position = 0; + + await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + await using TarReader reader = new(streamToRead); + TarEntry actualEntry = await reader.GetNextEntryAsync(); + Assert.NotNull(actualEntry); + + // GNU first writes the long link entry, containing: + // * 512 bytes of the regular tar header + // * 1234 bytes for the data section containing the full long link + // * 302 bytes of padding + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 2560. + // The regular file data section starts on the next byte. + long expectedDataOffset = canSeek ? 2560 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DataOffset_RegularFile_Async(bool canSeek) + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DataOffset_RegularFile_LongLink_LongPath(bool canSeek) + { + using MemoryStream ms = new(); + using (TarWriter writer = new(ms, leaveOpen: true)) { - await using MemoryStream ms = new(); - await using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - entry.DataStream = new MemoryStream(); - entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); - entry.DataStream.Position = 0; - await writer.WriteEntryAsync(entry); - } - ms.Position = 0; + string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry + GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongName); + entry.LinkName = new string('b', 1234); // Forces using a GNU LongLink entry + writer.WriteEntry(entry); + } + ms.Position = 0; + + using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + using TarReader reader = new(streamToRead); + TarEntry actualEntry = reader.GetNextEntry(); + Assert.NotNull(actualEntry); + + // GNU first writes the long link and long path entries, containing: + // * 512 bytes of the regular long link tar header + // * 1234 bytes for the data section containing the full long link + // * 302 bytes of padding + // * 512 bytes of the regular long path tar header + // * 1234 bytes for the data section containing the full long path + // * 302 bytes of padding + // Then it writes the actual entry, containing: + // * 512 bytes of the regular tar header + // Totalling 4608. + // The data section starts on the next byte. + long expectedDataOffset = canSeek ? 4608 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + } - await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - await using TarReader reader = new(streamToRead); - TarEntry actualEntry = await reader.GetNextEntryAsync(); - Assert.NotNull(actualEntry); + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DataOffset_RegularFile_LongLink_LongPath_Async(bool canSeek) + { + await using MemoryStream ms = new(); + await using (TarWriter writer = new(ms, leaveOpen: true)) + { + string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry + GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongName); + entry.LinkName = new string('b', 1234); // Forces using a GNU LongLink entry + await writer.WriteEntryAsync(entry); + } + ms.Position = 0; + + await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + await using TarReader reader = new(streamToRead); + TarEntry actualEntry = await reader.GetNextEntryAsync(); + Assert.NotNull(actualEntry); + + // GNU first writes the long link and long path entries, containing: + // * 512 bytes of the regular long link tar header + // * 1234 bytes for the data section containing the full long link + // * 302 bytes of padding + // * 512 bytes of the regular long path tar header + // * 1234 bytes for the data section containing the full long path + // * 302 bytes of padding + // Then it writes the actual entry, containing: + // * 512 bytes of the regular tar header + // Totalling 4608. + // The data section starts on the next byte. + long expectedDataOffset = canSeek ? 4608 : -1; + Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + } - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 2560. - // The regular file data section starts on the next byte. - long expectedDataOffset = canSeek ? 512 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); + [Fact] + public void DataOffset_BeforeAndAfterArchive() + { + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + Assert.Equal(-1, entry.DataOffset); + entry.DataStream = new MemoryStream(); + entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); + entry.DataStream.Position = 0; // The data stream is written to the archive from the current position + + using MemoryStream ms = new(); + using TarWriter writer = new(ms); + writer.WriteEntry(entry); + Assert.Equal(512, entry.DataOffset); + + // Write it again, the offset should now point to the second written entry + // First entry 512 (header) + 1 (data) + 511 (padding) + // Second entry 512 (header) + // 512 + 512 + 512 = 1536 + writer.WriteEntry(entry); + Assert.Equal(1536, entry.DataOffset); + } - if (canSeek) - { - ms.Position = actualEntry.DataOffset; - byte actualData = (byte)ms.ReadByte(); - Assert.Equal(ExpectedOffsetDataSingleByte, actualData); - } - } + [Fact] + public async Task DataOffset_BeforeAndAfterArchive_Async() + { + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + Assert.Equal(-1, entry.DataOffset); + + entry.DataStream = new MemoryStream(); + entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); + entry.DataStream.Position = 0; // The data stream is written to the archive from the current position + + await using MemoryStream ms = new(); + await using TarWriter writer = new(ms); + await writer.WriteEntryAsync(entry); + Assert.Equal(512, entry.DataOffset); + + // Write it again, the offset should now point to the second written entry + // First entry 512 (header) + 1 (data) + 511 (padding) + // Second entry 512 (header) + // 512 + 512 + 512 = 1536 + await writer.WriteEntryAsync(entry); + Assert.Equal(1536, entry.DataOffset); + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void DataOffset_RegularFile_LongPath(bool canSeek) - { - using MemoryStream ms = new(); - using (TarWriter writer = new(ms, leaveOpen: true)) - { - string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, veryLongName); - entry.DataStream = new MemoryStream(); - entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); - entry.DataStream.Position = 0; - writer.WriteEntry(entry); - } - ms.Position = 0; - - using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - using TarReader reader = new(streamToRead); - TarEntry actualEntry = reader.GetNextEntry(); - Assert.NotNull(actualEntry); - - // GNU first writes the long path entry, containing: - // * 512 bytes of the regular tar header - // * 1234 bytes for the data section containing the full long path - // * 302 bytes of padding - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 2560. - // The regular file data section starts on the next byte. - long expectedDataOffset = canSeek ? 2560 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DataOffset_RegularFile_LongPath_Async(bool canSeek) - { - await using MemoryStream ms = new(); - await using (TarWriter writer = new(ms, leaveOpen: true)) - { - string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, veryLongName); - entry.DataStream = new MemoryStream(); - entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); - entry.DataStream.Position = 0; - await writer.WriteEntryAsync(entry); - } - ms.Position = 0; - - await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - await using TarReader reader = new(streamToRead); - TarEntry actualEntry = await reader.GetNextEntryAsync(); - Assert.NotNull(actualEntry); - - // GNU first writes the long path entry, containing: - // * 512 bytes of the regular tar header - // * 1234 bytes for the data section containing the full long path - // * 302 bytes of padding - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 2560. - // The regular file data section starts on the next byte. - long expectedDataOffset = canSeek ? 2560 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void DataOffset_RegularFile_LongLink(bool canSeek) - { - using MemoryStream ms = new(); - using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, InitialEntryName); - entry.LinkName = new string('a', 1234); // Forces using a GNU LongLink entry - writer.WriteEntry(entry); - } - ms.Position = 0; - - using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - using TarReader reader = new(streamToRead); - TarEntry actualEntry = reader.GetNextEntry(); - Assert.NotNull(actualEntry); - - // GNU first writes the long link entry, containing: - // * 512 bytes of the regular tar header - // * 1234 bytes for the data section containing the full long link - // * 302 bytes of padding - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 2560. - // The regular file data section starts on the next byte. - long expectedDataOffset = canSeek ? 2560 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); - } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DataOffset_RegularFile_LongLink_Async(bool canSeek) - { - await using MemoryStream ms = new(); - await using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, InitialEntryName); - entry.LinkName = new string('b', 1234); // Forces using a GNU LongLink entry - await writer.WriteEntryAsync(entry); - } - ms.Position = 0; - - await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - await using TarReader reader = new(streamToRead); - TarEntry actualEntry = await reader.GetNextEntryAsync(); - Assert.NotNull(actualEntry); - - // GNU first writes the long link entry, containing: - // * 512 bytes of the regular tar header - // * 1234 bytes for the data section containing the full long link - // * 302 bytes of padding - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 2560. - // The regular file data section starts on the next byte. - long expectedDataOffset = canSeek ? 2560 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void DataOffset_RegularFile_LongLink_LongPath(bool canSeek) - { - using MemoryStream ms = new(); - using (TarWriter writer = new(ms, leaveOpen: true)) - { - string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry - GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongName); - entry.LinkName = new string('b', 1234); // Forces using a GNU LongLink entry - writer.WriteEntry(entry); - } - ms.Position = 0; - - using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - using TarReader reader = new(streamToRead); - TarEntry actualEntry = reader.GetNextEntry(); - Assert.NotNull(actualEntry); - - // GNU first writes the long link and long path entries, containing: - // * 512 bytes of the regular long link tar header - // * 1234 bytes for the data section containing the full long link - // * 302 bytes of padding - // * 512 bytes of the regular long path tar header - // * 1234 bytes for the data section containing the full long path - // * 302 bytes of padding - // Then it writes the actual entry, containing: - // * 512 bytes of the regular tar header - // Totalling 4608. - // The data section starts on the next byte. - long expectedDataOffset = canSeek ? 4608 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DataOffset_RegularFile_LongLink_LongPath_Async(bool canSeek) - { - await using MemoryStream ms = new(); - await using (TarWriter writer = new(ms, leaveOpen: true)) - { - string veryLongName = new string('a', 1234); // Forces using a GNU LongPath entry - GnuTarEntry entry = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongName); - entry.LinkName = new string('b', 1234); // Forces using a GNU LongLink entry - await writer.WriteEntryAsync(entry); - } - ms.Position = 0; - - await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - await using TarReader reader = new(streamToRead); - TarEntry actualEntry = await reader.GetNextEntryAsync(); - Assert.NotNull(actualEntry); - - // GNU first writes the long link and long path entries, containing: - // * 512 bytes of the regular long link tar header - // * 1234 bytes for the data section containing the full long link - // * 302 bytes of padding - // * 512 bytes of the regular long path tar header - // * 1234 bytes for the data section containing the full long path - // * 302 bytes of padding - // Then it writes the actual entry, containing: - // * 512 bytes of the regular tar header - // Totalling 4608. - // The data section starts on the next byte. - long expectedDataOffset = canSeek ? 4608 : -1; - Assert.Equal(expectedDataOffset, actualEntry.DataOffset); - } - - [Fact] - public void DataOffset_BeforeAndAfterArchive() + [Fact] + public void DataOffset_UnseekableDataStream() + { + using MemoryStream ms = new(); + using (TarWriter writer = new(ms, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); Assert.Equal(-1, entry.DataOffset); - entry.DataStream = new MemoryStream(); - entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); - entry.DataStream.Position = 0; // The data stream is written to the archive from the current position - using MemoryStream ms = new(); - using TarWriter writer = new(ms); - writer.WriteEntry(entry); - Assert.Equal(512, entry.DataOffset); + using MemoryStream dataStream = new(); + dataStream.WriteByte(ExpectedOffsetDataSingleByte); + dataStream.Position = 0; + using WrappedStream wds = new(dataStream, canWrite: true, canRead: true, canSeek: false); + entry.DataStream = wds; - // Write it again, the offset should now point to the second written entry - // First entry 512 (header) + 1 (data) + 511 (padding) - // Second entry 512 (header) - // 512 + 512 + 512 = 1536 writer.WriteEntry(entry); - Assert.Equal(1536, entry.DataOffset); } + ms.Position = 0; + + using TarReader reader = new(ms); + TarEntry actualEntry = reader.GetNextEntry(); + Assert.NotNull(actualEntry); + // Gnu header length is 512, data starts in the next position + Assert.Equal(512, actualEntry.DataOffset); + } - [Fact] - public async Task DataOffset_BeforeAndAfterArchive_Async() + [Fact] + public async Task DataOffset_UnseekableDataStream_Async() + { + await using MemoryStream ms = new(); + await using (TarWriter writer = new(ms, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); Assert.Equal(-1, entry.DataOffset); - entry.DataStream = new MemoryStream(); - entry.DataStream.WriteByte(ExpectedOffsetDataSingleByte); - entry.DataStream.Position = 0; // The data stream is written to the archive from the current position + await using MemoryStream dataStream = new(); + dataStream.WriteByte(ExpectedOffsetDataSingleByte); + dataStream.Position = 0; + await using WrappedStream wds = new(dataStream, canWrite: true, canRead: true, canSeek: false); + entry.DataStream = wds; - await using MemoryStream ms = new(); - await using TarWriter writer = new(ms); await writer.WriteEntryAsync(entry); - Assert.Equal(512, entry.DataOffset); + } + ms.Position = 0; - // Write it again, the offset should now point to the second written entry - // First entry 512 (header) + 1 (data) + 511 (padding) - // Second entry 512 (header) - // 512 + 512 + 512 = 1536 - await writer.WriteEntryAsync(entry); - Assert.Equal(1536, entry.DataOffset); + await using TarReader reader = new(ms); + TarEntry actualEntry = await reader.GetNextEntryAsync(); + Assert.NotNull(actualEntry); + // Gnu header length is 512, data starts in the next position + Assert.Equal(512, actualEntry.DataOffset); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DataOffset_LongPath_LongLink_SecondEntry(bool canSeek) + { + string veryLongPathName = new string('a', 1234); // Forces using a GNU LongPath entry + string veryLongLinkName = new string('b', 1234); // Forces using a GNU LongLink entry + + using MemoryStream ms = new(); + using (TarWriter writer = new(ms, leaveOpen: true)) + { + GnuTarEntry entry1 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); + entry1.LinkName = veryLongLinkName; + writer.WriteEntry(entry1); + + GnuTarEntry entry2 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); + entry2.LinkName = veryLongLinkName; + writer.WriteEntry(entry2); } + ms.Position = 0; + + using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + using TarReader reader = new(streamToRead); + TarEntry firstEntry = reader.GetNextEntry(); + Assert.NotNull(firstEntry); + // GNU first writes the long link and long path entries, containing: + // * 512 bytes of the regular long link tar header + // * 1234 bytes for the data section containing the full long link + // * 302 bytes of padding + // * 512 bytes of the regular long path tar header + // * 1234 bytes for the data section containing the full long path + // * 302 bytes of padding + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 4608. + // The regular file data section starts on the next byte. + long firstExpectedDataOffset = canSeek ? 4608 : -1; + Assert.Equal(firstExpectedDataOffset, firstEntry.DataOffset); + + TarEntry secondEntry = reader.GetNextEntry(); + Assert.NotNull(secondEntry); + // First entry (including its long link and long path entries) end at 4608 (no padding, empty, as symlink has no data) + // Second entry (including its long link and long path entries) data section starts one byte after 4608 + 4608 = 9216 + long secondExpectedDataOffset = canSeek ? 9216 : -1; + Assert.Equal(secondExpectedDataOffset, secondEntry.DataOffset); + } - [Fact] - public void DataOffset_UnseekableDataStream() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DataOffset_LongPath_LongLink_SecondEntry_Async(bool canSeek) + { + string veryLongPathName = new string('a', 1234); // Forces using a GNU LongPath entry + string veryLongLinkName = new string('b', 1234); // Forces using a GNU LongLink entry + + await using MemoryStream ms = new(); + await using (TarWriter writer = new(ms, leaveOpen: true)) { - using MemoryStream ms = new(); - using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - Assert.Equal(-1, entry.DataOffset); + GnuTarEntry entry1 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); + entry1.LinkName = veryLongLinkName; + await writer.WriteEntryAsync(entry1); - using MemoryStream dataStream = new(); - dataStream.WriteByte(ExpectedOffsetDataSingleByte); - dataStream.Position = 0; - using WrappedStream wds = new(dataStream, canWrite: true, canRead: true, canSeek: false); - entry.DataStream = wds; + GnuTarEntry entry2 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); + entry2.LinkName = veryLongLinkName; + await writer.WriteEntryAsync(entry2); + } + ms.Position = 0; + + await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); + await using TarReader reader = new(streamToRead); + TarEntry firstEntry = await reader.GetNextEntryAsync(); + Assert.NotNull(firstEntry); + // GNU first writes the long link and long path entries, containing: + // * 512 bytes of the regular long link tar header + // * 1234 bytes for the data section containing the full long link + // * 302 bytes of padding + // * 512 bytes of the regular long path tar header + // * 1234 bytes for the data section containing the full long path + // * 302 bytes of padding + // Then it writes the actual regular file entry, containing: + // * 512 bytes of the regular tar header + // Totalling 4608. + // The regular file data section starts on the next byte. + long firstExpectedDataOffset = canSeek ? 4608 : -1; + Assert.Equal(firstExpectedDataOffset, firstEntry.DataOffset); + + TarEntry secondEntry = await reader.GetNextEntryAsync(); + Assert.NotNull(secondEntry); + // First entry (including its long link and long path entries) end at 4608 (no padding, empty, as symlink has no data) + // Second entry (including its long link and long path entries) data section starts one byte after 4608 + 4608 = 9216 + long secondExpectedDataOffset = canSeek ? 9216 : -1; + Assert.Equal(secondExpectedDataOffset, secondEntry.DataOffset); + } - writer.WriteEntry(entry); - } - ms.Position = 0; + [Fact] + public void UnusedBytesInSizeFieldShouldBeZeroChars() + { + // The GNU format sets the unused bytes in the size field to "0" characters. - using TarReader reader = new(ms); - TarEntry actualEntry = reader.GetNextEntry(); - Assert.NotNull(actualEntry); - // Gnu header length is 512, data starts in the next position - Assert.Equal(512, actualEntry.DataOffset); + using MemoryStream ms = new(); + + using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) + { + // Start with a regular file entry without data + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + writer.WriteEntry(entry); } + ms.Position = 0; - [Fact] - public async Task DataOffset_UnseekableDataStream_Async() + using (TarReader reader = new(ms, leaveOpen: true)) { - await using MemoryStream ms = new(); - await using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - Assert.Equal(-1, entry.DataOffset); + GnuTarEntry entry = reader.GetNextEntry() as GnuTarEntry; + Assert.NotNull(entry); + Assert.Equal(0, entry.Length); + } + ms.Position = 0; + ValidateUnusedBytesInSizeField(ms, 0); + + ms.SetLength(0); // Reset the stream + byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; // 8 bytes of data means a size of 10 in octal + using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) + { + // Start with a regular file entry with data + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + entry.DataStream = new MemoryStream(data); + writer.WriteEntry(entry); + } - await using MemoryStream dataStream = new(); - dataStream.WriteByte(ExpectedOffsetDataSingleByte); - dataStream.Position = 0; - await using WrappedStream wds = new(dataStream, canWrite: true, canRead: true, canSeek: false); - entry.DataStream = wds; + ms.Position = 0; + using (TarReader reader = new(ms, leaveOpen: true)) + { + GnuTarEntry entry = reader.GetNextEntry() as GnuTarEntry; + Assert.NotNull(entry); + Assert.Equal(data.Length, entry.Length); + } + ms.Position = 0; + ValidateUnusedBytesInSizeField(ms, data.Length); + } - await writer.WriteEntryAsync(entry); - } - ms.Position = 0; + [Fact] + public async Task UnusedBytesInSizeFieldShouldBeZeroChars_Async() + { + // The GNU format sets the unused bytes in the size field to "0" characters. - await using TarReader reader = new(ms); - TarEntry actualEntry = await reader.GetNextEntryAsync(); - Assert.NotNull(actualEntry); - // Gnu header length is 512, data starts in the next position - Assert.Equal(512, actualEntry.DataOffset); + await using MemoryStream ms = new(); + + await using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) + { + // Start with a regular file entry without data + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + await writer.WriteEntryAsync(entry); } + ms.Position = 0; - [Theory] - [InlineData(false)] - [InlineData(true)] - public void DataOffset_LongPath_LongLink_SecondEntry(bool canSeek) + await using (TarReader reader = new(ms, leaveOpen: true)) { - string veryLongPathName = new string('a', 1234); // Forces using a GNU LongPath entry - string veryLongLinkName = new string('b', 1234); // Forces using a GNU LongLink entry + GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry; + Assert.NotNull(entry); + Assert.Equal(0, entry.Length); + } + ms.Position = 0; + ValidateUnusedBytesInSizeField(ms, 0); - using MemoryStream ms = new(); - using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry1 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); - entry1.LinkName = veryLongLinkName; - writer.WriteEntry(entry1); + ms.SetLength(0); // Reset the stream + byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; // 8 bytes of data means a size of 10 in octal + await using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) + { + // Start with a regular file entry with data + GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + entry.DataStream = new MemoryStream(data); + await writer.WriteEntryAsync(entry); + } - GnuTarEntry entry2 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); - entry2.LinkName = veryLongLinkName; - writer.WriteEntry(entry2); - } - ms.Position = 0; - - using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - using TarReader reader = new(streamToRead); - TarEntry firstEntry = reader.GetNextEntry(); - Assert.NotNull(firstEntry); - // GNU first writes the long link and long path entries, containing: - // * 512 bytes of the regular long link tar header - // * 1234 bytes for the data section containing the full long link - // * 302 bytes of padding - // * 512 bytes of the regular long path tar header - // * 1234 bytes for the data section containing the full long path - // * 302 bytes of padding - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 4608. - // The regular file data section starts on the next byte. - long firstExpectedDataOffset = canSeek ? 4608 : -1; - Assert.Equal(firstExpectedDataOffset, firstEntry.DataOffset); - - TarEntry secondEntry = reader.GetNextEntry(); - Assert.NotNull(secondEntry); - // First entry (including its long link and long path entries) end at 4608 (no padding, empty, as symlink has no data) - // Second entry (including its long link and long path entries) data section starts one byte after 4608 + 4608 = 9216 - long secondExpectedDataOffset = canSeek ? 9216 : -1; - Assert.Equal(secondExpectedDataOffset, secondEntry.DataOffset); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task DataOffset_LongPath_LongLink_SecondEntry_Async(bool canSeek) - { - string veryLongPathName = new string('a', 1234); // Forces using a GNU LongPath entry - string veryLongLinkName = new string('b', 1234); // Forces using a GNU LongLink entry - - await using MemoryStream ms = new(); - await using (TarWriter writer = new(ms, leaveOpen: true)) - { - GnuTarEntry entry1 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); - entry1.LinkName = veryLongLinkName; - await writer.WriteEntryAsync(entry1); + ms.Position = 0; + await using (TarReader reader = new(ms, leaveOpen: true)) + { + GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry; + Assert.NotNull(entry); + Assert.Equal(data.Length, entry.Length); + } + ms.Position = 0; + ValidateUnusedBytesInSizeField(ms, data.Length); + } - GnuTarEntry entry2 = new GnuTarEntry(TarEntryType.SymbolicLink, veryLongPathName); - entry2.LinkName = veryLongLinkName; - await writer.WriteEntryAsync(entry2); - } - ms.Position = 0; - - await using Stream streamToRead = new WrappedStream(ms, canWrite: true, canRead: true, canSeek: canSeek); - await using TarReader reader = new(streamToRead); - TarEntry firstEntry = await reader.GetNextEntryAsync(); - Assert.NotNull(firstEntry); - // GNU first writes the long link and long path entries, containing: - // * 512 bytes of the regular long link tar header - // * 1234 bytes for the data section containing the full long link - // * 302 bytes of padding - // * 512 bytes of the regular long path tar header - // * 1234 bytes for the data section containing the full long path - // * 302 bytes of padding - // Then it writes the actual regular file entry, containing: - // * 512 bytes of the regular tar header - // Totalling 4608. - // The regular file data section starts on the next byte. - long firstExpectedDataOffset = canSeek ? 4608 : -1; - Assert.Equal(firstExpectedDataOffset, firstEntry.DataOffset); - - TarEntry secondEntry = await reader.GetNextEntryAsync(); - Assert.NotNull(secondEntry); - // First entry (including its long link and long path entries) end at 4608 (no padding, empty, as symlink has no data) - // Second entry (including its long link and long path entries) data section starts one byte after 4608 + 4608 = 9216 - long secondExpectedDataOffset = canSeek ? 9216 : -1; - Assert.Equal(secondExpectedDataOffset, secondEntry.DataOffset); - } - - [Fact] - public void UnusedBytesInSizeFieldShouldBeZeroChars() - { - // The GNU format sets the unused bytes in the size field to "0" characters. - - using MemoryStream ms = new(); - - using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) - { - // Start with a regular file entry without data - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - writer.WriteEntry(entry); - } - ms.Position = 0; + private void ValidateUnusedBytesInSizeField(MemoryStream ms, long size) + { + // internally, the unused bytes in the size field should be "0" characters, + // and the rest should be the octal value of the size field + + // name, mode, uid, gid, + // size + int sizeStart = 100 + 8 + 8 + 8; + byte[] buffer = new byte[12]; // The size field is 12 bytes in length + + ms.Seek(sizeStart, SeekOrigin.Begin); + ms.Read(buffer); + + // Convert the base 10 value of size to base 8 + string octalSize = Convert.ToString(size, 8).PadLeft(11, '0'); + byte[] octalSizeBytes = Encoding.ASCII.GetBytes(octalSize); + // The last byte should be a null character + Assert.Equal(octalSizeBytes, buffer.Take(octalSizeBytes.Length).ToArray()); + } - using (TarReader reader = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = reader.GetNextEntry() as GnuTarEntry; - Assert.NotNull(entry); - Assert.Equal(0, entry.Length); - } - ms.Position = 0; - ValidateUnusedBytesInSizeField(ms, 0); + public static IEnumerable NameAndLink_TestData() + { + // Name and link have a max length of 100. Anything longer goes into LongPath or LongLink entries in GNU. + yield return new object[] { InitialEntryName, InitialEntryName }; // Short name and short link + yield return new object[] { InitialEntryName, new string('b', 101) }; // Short name and long link + yield return new object[] { new string('a', 101), InitialEntryName }; // Long name and short link + yield return new object[] { new string('a', 101), new string('b', 101) }; // Long name and long link + } - ms.SetLength(0); // Reset the stream - byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; // 8 bytes of data means a size of 10 in octal - using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) - { - // Start with a regular file entry with data - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - entry.DataStream = new MemoryStream(data); - writer.WriteEntry(entry); - } + private GnuTarEntry CreateEntryForLongLinkLongPathChecks(string name, string linkName) + { + // A SymbolicLink entry can test both LongLink and LongPath entries if + // the length of either string is longer than what fits in the header. + return new GnuTarEntry(TarEntryType.SymbolicLink, name) + { + LinkName = linkName, + ModificationTime = DateTimeOffset.UnixEpoch, + AccessTime = DateTimeOffset.UnixEpoch, + ChangeTime = DateTimeOffset.UnixEpoch, + Uid = 0, + Gid = 0, + Mode = 0, + UserName = TestUName, + GroupName = TestGName + }; + } - ms.Position = 0; - using (TarReader reader = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = reader.GetNextEntry() as GnuTarEntry; - Assert.NotNull(entry); - Assert.Equal(data.Length, entry.Length); - } - ms.Position = 0; - ValidateUnusedBytesInSizeField(ms, data.Length); + private void ValidateEntryForRegularEntryInLongLinkAndLongPathChecks(GnuTarEntry entry, string name, string linkName) + { + Assert.NotNull(entry); + Assert.Equal(DateTimeOffset.UnixEpoch, entry.ModificationTime); + Assert.Equal(DateTimeOffset.UnixEpoch, entry.AccessTime); + Assert.Equal(DateTimeOffset.UnixEpoch, entry.ChangeTime); + Assert.Equal(name, entry.Name); + Assert.Equal(linkName, entry.LinkName); + Assert.Equal(0, entry.Uid); // Should be '0' chars in the long header + Assert.Equal(0, entry.Gid); // Should be '0' chars in the long header + Assert.Equal(UnixFileMode.None, entry.Mode); // Should be '0' chars in the long header + Assert.Equal(TestUName, entry.UserName); + Assert.Equal(TestGName, entry.GroupName); + Assert.Equal(0, entry.Length); // No data in the main entry + } + + [Theory] + [MemberData(nameof(NameAndLink_TestData))] + public void Check_LongLink_AndLongPath_Metadata(string name, string linkName) + { + // The GNU format sets the mtime, atime and ctime to nulls in headers when they are set to the unix epoch. + // Also the uid and gid should be '0' in the long entries headers. + // Also the uname and gname in the long entry headers should be set to those of the main entry. + + using MemoryStream ms = new(); + + using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) + { + GnuTarEntry entry = CreateEntryForLongLinkLongPathChecks(name, linkName); + writer.WriteEntry(entry); } + ms.Position = 0; - [Fact] - public async Task UnusedBytesInSizeFieldShouldBeZeroChars_Async() + using (TarReader reader = new(ms, leaveOpen: true)) { - // The GNU format sets the unused bytes in the size field to "0" characters. + GnuTarEntry entry = reader.GetNextEntry() as GnuTarEntry; + ValidateEntryForRegularEntryInLongLinkAndLongPathChecks(entry, name, linkName); + } - await using MemoryStream ms = new(); + ValidateLongEntryBytes(ms, name, linkName); + } - await using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) - { - // Start with a regular file entry without data - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - await writer.WriteEntryAsync(entry); - } - ms.Position = 0; + [Theory] + [MemberData(nameof(NameAndLink_TestData))] + public async Task Check_LongLink_AndLongPath_Metadata_Async(string name, string linkName) + { + // The GNU format sets the mtime, atime and ctime to nulls in headers when they are set to the unix epoch. + // Also the uid and gid should be '0' in the long entries headers. + // Also the uname and gname in the long entry headers should be set to those of the main entry. - await using (TarReader reader = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry; - Assert.NotNull(entry); - Assert.Equal(0, entry.Length); - } - ms.Position = 0; - ValidateUnusedBytesInSizeField(ms, 0); + await using MemoryStream ms = new(); - ms.SetLength(0); // Reset the stream - byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; // 8 bytes of data means a size of 10 in octal - await using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) - { - // Start with a regular file entry with data - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); - entry.DataStream = new MemoryStream(data); - await writer.WriteEntryAsync(entry); - } + await using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) + { + GnuTarEntry entry = CreateEntryForLongLinkLongPathChecks(name, linkName); + await writer.WriteEntryAsync(entry); + } + ms.Position = 0; - ms.Position = 0; - await using (TarReader reader = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry; - Assert.NotNull(entry); - Assert.Equal(data.Length, entry.Length); - } - ms.Position = 0; - ValidateUnusedBytesInSizeField(ms, data.Length); + await using (TarReader reader = new(ms, leaveOpen: true)) + { + GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry; + ValidateEntryForRegularEntryInLongLinkAndLongPathChecks(entry, name, linkName); } - private void ValidateUnusedBytesInSizeField(MemoryStream ms, long size) + ValidateLongEntryBytes(ms, name, linkName); + } + + private void ValidateLongEntryBytes(MemoryStream ms, string name, string linkName) + { + bool isLongPath = name.Length >= 100; + bool isLongLink = linkName.Length >= 100; + + ms.Position = 0; + + long nextEntryStart = 0; + long reportedSize; + + if (isLongLink) { - // internally, the unused bytes in the size field should be "0" characters, - // and the rest should be the octal value of the size field + reportedSize = CheckHeaderMetadataAndGetReportedSize(ms, nextEntryStart, isLongLinkOrLongPath: true); + CheckDataContainsExpectedString(ms, nextEntryStart + 512, reportedSize, linkName, shouldTrim: false); // Skip to the data section + nextEntryStart += 512 + 512; // Skip the current header and the long link entry + Assert.True(linkName.Length < 512, "Do not test paths longer than a 512 byte block"); + } - // name, mode, uid, gid, - // size - int sizeStart = 100 + 8 + 8 + 8; - byte[] buffer = new byte[12]; // The size field is 12 bytes in length + if (isLongPath) + { + reportedSize = CheckHeaderMetadataAndGetReportedSize(ms, nextEntryStart, isLongLinkOrLongPath: true); + CheckDataContainsExpectedString(ms, nextEntryStart + 512, reportedSize, name, shouldTrim: false); // Skip to the data section + nextEntryStart += 512 + 512; // Skip the current header and the long path entry + Assert.True(name.Length < 512, "Do not test paths longer than a 512 byte block"); + } - ms.Seek(sizeStart, SeekOrigin.Begin); - ms.Read(buffer); + CheckHeaderMetadataAndGetReportedSize(ms, nextEntryStart, isLongLinkOrLongPath: false); + } - // Convert the base 10 value of size to base 8 - string octalSize = Convert.ToString(size, 8).PadLeft(11, '0'); - byte[] octalSizeBytes = Encoding.ASCII.GetBytes(octalSize); - // The last byte should be a null character - Assert.Equal(octalSizeBytes, buffer.Take(octalSizeBytes.Length).ToArray()); + private long CheckHeaderMetadataAndGetReportedSize(MemoryStream ms, long nextEntryStart, bool isLongLinkOrLongPath) + { + // internally, mtime, atime and ctime should be nulls + // and if the entry is a long path or long link, the entry's data length should be + // equal to the string plus a null character + + // name mode uid gid size mtime checksum typeflag linkname magic uname gname devmajor devminor atime ctime + // 100 8 8 8 12 12 8 1 100 8 32 32 8 8 12 12 + long nameStart = nextEntryStart; + long modeStart = nameStart + 100; + long uidStart = modeStart + 8; + long gidStart = uidStart + 8; + long sizeStart = gidStart + 8; + long mTimeStart = sizeStart + 12; + long checksumStart = mTimeStart + 12; + long typeflagStart = checksumStart + 8; + long linkNameStart = typeflagStart + 1; + long magicStart = linkNameStart + 100; + long uNameStart = magicStart + 8; + long gNameStart = uNameStart + 32; + long devMajorStart = gNameStart + 32; + long devMinorStart = devMajorStart + 8; + long aTimeStart = devMinorStart + 8; + long cTimeStart = aTimeStart + 12; + + byte[] buffer = new byte[12]; // size, mtime, atime, ctime all are 12 bytes in length + + if (isLongLinkOrLongPath) + { + CheckBytesAreZeros(ms, buffer.AsSpan(0, 8), uidStart); + CheckBytesAreZeros(ms, buffer.AsSpan(0, 8), gidStart); + } + else + { + CheckBytesAreZeros(ms, buffer.AsSpan(0, 8), modeStart); } + CheckBytesAreZeros(ms, buffer, mTimeStart); + CheckBytesAreZeros(ms, buffer, aTimeStart); + CheckBytesAreZeros(ms, buffer, cTimeStart); + CheckDataContainsExpectedString(ms, uNameStart, 32, TestUName, shouldTrim: true); + CheckDataContainsExpectedString(ms, gNameStart, 32, TestGName, shouldTrim: true); + + ms.Seek(sizeStart, SeekOrigin.Begin); + ms.Read(buffer); + return ParseNumeric(buffer); + } - [Fact] - public void ATime_CTime_Epochs_ShouldBeNulls() + private void CheckBytesAreSpecificChar(MemoryStream ms, Span buffer, long dataStart, byte charToCheck) + { + ms.Seek(dataStart, SeekOrigin.Begin); + ms.Read(buffer); + foreach (byte b in buffer.Slice(0, buffer.Length - 1)) // The last byte should be a null character { - // The GNU format sets the access and change times to nulls when they are set to the unix epoch. + Assert.Equal(charToCheck, b); + } + Assert.Equal(0, buffer[^1]); // The last byte should be a null character + } - using MemoryStream ms = new(); + private void CheckBytesAreNulls(MemoryStream ms, Span buffer, long dataStart) => CheckBytesAreSpecificChar(ms, buffer, dataStart, 0); // null char - using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) - { - // Should not be "0" characters, but null characters - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName) - { - AccessTime = DateTimeOffset.UnixEpoch, - ChangeTime = DateTimeOffset.UnixEpoch - }; - writer.WriteEntry(entry); - } - ms.Position = 0; + private void CheckBytesAreZeros(MemoryStream ms, Span buffer, long dataStart) => CheckBytesAreSpecificChar(ms, buffer, dataStart, 0x30); // '0' char - // Publicly they should be the unix epoch - using (TarReader reader = new(ms, leaveOpen: true)) - { - GnuTarEntry entry = reader.GetNextEntry() as GnuTarEntry; - Assert.NotNull(entry); - Assert.Equal(DateTimeOffset.UnixEpoch, entry.AccessTime); - Assert.Equal(DateTimeOffset.UnixEpoch, entry.ChangeTime); - } + private void CheckDataContainsExpectedString(MemoryStream ms, long dataStart, long actualDataLength, string expectedString, bool shouldTrim) + { + ms.Seek(dataStart, SeekOrigin.Begin); + byte[] buffer = new byte[actualDataLength]; + ms.Read(buffer); - ValidateATimeAndCTimeBytes(ms); + if (shouldTrim) + { + string actualString = Encoding.ASCII.GetString(TrimEndingNullsAndSpaces(buffer)); + Assert.Equal(expectedString, actualString); + } + else + { + string actualString = Encoding.ASCII.GetString(buffer); + Assert.Equal(expectedString, actualString[..^1]); // The last byte should be a null character } + } - [Fact] - public async Task ATime_CTime_Epochs_ShouldBeNulls_Async() + private static T ParseNumeric(ReadOnlySpan buffer) where T : struct, INumber, IBinaryInteger + { + byte leadingByte = buffer[0]; + if (leadingByte == 0xff) + { + return T.ReadBigEndian(buffer, isUnsigned: false); + } + else if (leadingByte == 0x80) { - await using MemoryStream ms = new(); + return T.ReadBigEndian(buffer.Slice(1), isUnsigned: true); + } + else + { + return ParseOctal(buffer); + } + } - await using (TarWriter writer = new(ms, TarEntryFormat.Gnu, leaveOpen: true)) - { - // Should not be "0" characters, but null characters - GnuTarEntry entry = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName) - { - AccessTime = DateTimeOffset.UnixEpoch, - ChangeTime = DateTimeOffset.UnixEpoch - }; - await writer.WriteEntryAsync(entry); - } - ms.Position = 0; + private static T ParseOctal(ReadOnlySpan buffer) where T : struct, INumber + { + buffer = TrimEndingNullsAndSpaces(buffer); + buffer = TrimLeadingNullsAndSpaces(buffer); - // Publicly they should be the unix epoch - await using (TarReader reader = new(ms, leaveOpen: true)) + if (buffer.Length == 0) + { + return T.Zero; + } + + T octalFactor = T.CreateTruncating(8u); + T value = T.Zero; + foreach (byte b in buffer) + { + uint digit = (uint)(b - '0'); + if (digit >= 8) { - GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry; - Assert.NotNull(entry); - Assert.Equal(DateTimeOffset.UnixEpoch, entry.AccessTime); - Assert.Equal(DateTimeOffset.UnixEpoch, entry.ChangeTime); + throw new InvalidDataException(SR.Format(SR.TarInvalidNumber)); } - ValidateATimeAndCTimeBytes(ms); + value = checked((value * octalFactor) + T.CreateTruncating(digit)); } - private void ValidateATimeAndCTimeBytes(MemoryStream ms) - { - // internally, atime and ctime should be nulls - - // name, mode, uid, gid, size, mtime, checksum, typeflag, linkname, magic, uname, gname, devmajor, devminor, - // atime, ctime - int aTimeStart = 100 + 8 + 8 + 8 + 12 + 12 + 8 + 1 + 100 + 8 + 32 + 32 + 8 + 8; - int cTimeStart = aTimeStart + 12; - byte[] buffer = new byte[12]; // atime and ctime are 12 bytes in length + return value; + } - ms.Seek(aTimeStart, SeekOrigin.Begin); - ms.Read(buffer); + private static ReadOnlySpan TrimEndingNullsAndSpaces(ReadOnlySpan buffer) + { + int trimmedLength = buffer.Length; + while (trimmedLength > 0 && buffer[trimmedLength - 1] is 0 or 32) + { + trimmedLength--; + } - Assert.All(buffer, b => Assert.Equal(0, b)); // All should be nulls + return buffer.Slice(0, trimmedLength); + } - ms.Seek(cTimeStart, SeekOrigin.Begin); - ms.Read(buffer); - Assert.All(buffer, b => Assert.Equal(0, b)); // All should be nulls + private static ReadOnlySpan TrimLeadingNullsAndSpaces(ReadOnlySpan buffer) + { + int newStart = 0; + while (newStart < buffer.Length && buffer[newStart] is 0 or 32) + { + newStart++; } + + return buffer.Slice(newStart); } } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs index 23ba1bc4733fd3..1dbd3fc9fdf7bb 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs @@ -363,7 +363,7 @@ private int GetChecksumForCommonFields(TarEntry entry, TarEntryType entryType) if (entryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) { entry.DataStream = new MemoryStream(); - byte[] buffer = new byte[] { 72, 101, 108, 108, 111 }; // values don't matter, only length (5) + byte[] buffer = [ 72, 101, 108, 108, 111 ]; // values don't matter, only length (5) // '0000000005\0' = 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 53 + 0 = 533 entry.DataStream.Write(buffer); @@ -377,9 +377,9 @@ private int GetChecksumForCommonFields(TarEntry entry, TarEntryType entryType) } // If V7 regular file: '\0' = 0 - // If Ustar/Pax/Gnu regular file: '0' = 48 - // If block device: '4' = 52 - expectedChecksum += (byte)entryType; + // If Ustar/Pax/Gnu regular file: '0' = 48 + // If block device: '4' = 52 + expectedChecksum += (byte)entryType; // Checksum so far: 256 + 351 + 353 + 359 + 571 = decimal 1890 // If V7RegularFile: 1890 + 533 + 0 = 2423 (octal 4567) => '004567\0' @@ -440,7 +440,7 @@ private int GetChecksumForFormatSpecificFields(TarEntry entry, TarEntryFormat fo gnuEntry.ChangeTime = expectedTimestampToTest; checksum += expectedTimestampChecksumToTest; - // Total: UnixEpoch = 0, otherwise = 1142 + // Total: UnixEpoch = 1056, otherwise = 1142 void GetTimestampToTest(bool testEpoch, out DateTimeOffset expectedTimestampToTest, out int expectedTimestampChecksumToTest) { @@ -452,8 +452,8 @@ void GetTimestampToTest(bool testEpoch, out DateTimeOffset expectedTimestampToTe expectedTimestampToTest = TimestampForChecksum; // ToUnixTimeSeconds() = decimal 1641095100, octal 14164217674; } - expectedTimestampChecksumToTest = 0; - // '\0\0\0\0\0\0\0\0\0\0\0\0' = 0 + expectedTimestampChecksumToTest = 528; + // '00000000000\0' = 0 expectedTimestampToTest = UnixEpochTimestampForChecksum; // ToUnixTimeSeconds() = decimal 0, octal 0; } } @@ -466,7 +466,9 @@ void GetTimestampToTest(bool testEpoch, out DateTimeOffset expectedTimestampToTe // Gnu RegularFile: 623 + 1004 + 1142 = 2769 // Ustar BlockDevice: 655 + 1004 + 686 = 2345 // Pax BlockDevice: 655 + 1004 + 686 = 2345 - // Gnu BlockDevice: 623 + 1004 + 686 + 1142 = 3455 + // Gnu BlockDevice: + // No epoch: 623 + 1004 + 686 + 1142 = 3455 + // Epoch: 623 + 1004 + 686 + 1056 = 3369 return checksum; } } From 95fb34415333dc76c1ba886e2043cacc17684127 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 11:54:34 -0700 Subject: [PATCH 2/5] Run outerloop pipeline only for release branches, not staging/preview (#115010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I noticed we were sending 30k workitems each day to various Helix queues, this is due to the outerloop pipeline having a schedule triggere which matches old preview branches or the staging branches. This fixes that and also sets it so that the trigger only applies if there are actual source changes. Co-authored-by: Alexander Köplinger --- eng/pipelines/libraries/outerloop-mono.yml | 1 + eng/pipelines/libraries/outerloop.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/eng/pipelines/libraries/outerloop-mono.yml b/eng/pipelines/libraries/outerloop-mono.yml index 329de2ea9267e3..929a02ed080482 100644 --- a/eng/pipelines/libraries/outerloop-mono.yml +++ b/eng/pipelines/libraries/outerloop-mono.yml @@ -6,6 +6,7 @@ schedules: branches: include: - main + always: false # run only if there were changes since the last successful scheduled run. variables: - template: variables.yml diff --git a/eng/pipelines/libraries/outerloop.yml b/eng/pipelines/libraries/outerloop.yml index bbb1adc12cde55..afc38926ea35eb 100644 --- a/eng/pipelines/libraries/outerloop.yml +++ b/eng/pipelines/libraries/outerloop.yml @@ -6,7 +6,8 @@ schedules: branches: include: - main - - release/*.* + - release/*.0 + always: false # run only if there were changes since the last successful scheduled run. variables: - template: variables.yml From 1e69c98f8666b6a238206cf95febcd89c97e7f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 24 Apr 2025 22:23:08 +0200 Subject: [PATCH 3/5] Ensure compression targets run after preloading (#114960) Customers using WebAssembly preloading (on by default) loses the ability to use pre-compressed asset endpoint for preloaded asset in Blazor Web scenario. --- .../build/Microsoft.NET.Sdk.WebAssembly.Browser.targets | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 3e781a14d8a61b..60a69f64fb3eaf 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -107,6 +107,7 @@ Copyright (c) .NET Foundation. All rights reserved. ResolveWasmOutputs; _GenerateBuildWasmBootJson; _AddWasmStaticWebAssets; + _AddWasmPreloadBuildProperties; $(ResolveCompressedFilesForPublishDependsOn); @@ -114,6 +115,7 @@ Copyright (c) .NET Foundation. All rights reserved. ComputeWasmExtensions; GeneratePublishWasmBootJson; _AddPublishWasmBootJsonToStaticWebAssets; + _AddWasmPreloadPublishProperties; $(CompressFilesForPublishDependsOn); From 49efa9bf1ea81bc72360e0fd6ffeb4a054b4fee3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:15:23 -0700 Subject: [PATCH 4/5] [release/10.0-preview4] Rewrite of System.Numerics.Tensors to allow for more code sharing, various correctness fixes, and stability improvements (#114979) * Refactor Tensor to be more reusable and validate appropriate state * Handle Equals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, and the *All/*Any variants * Many implementations correctly swapped to new form. (#25) * Refactor Tensor to be more reusable and validate appropriate state * finishing tensor primitives work --------- Co-authored-by: Tanner Gooding * more tensors updates (#26) * Resolve a few build failures * Ensure SetSlice and ToString are working as expected * Tensors lastfew (#27) * only couple left * pausing for food * fixed rented buffer * squeeze/unsqueeze * set slice/ split * only 2 left * Minor cleanup of the Tensor files * Ensure that tensor tests are building * Resolving various build failures due to API compatibility * Ensure flattendLength is adjusted after the stride is set for that dimension * Ensure that we set linearLength if -1 is passed in when strides is empty * Ensure that the first index is correct * Cleanup to ensure iteration and construction initializes correctly * Ensure that broadcasting is allowed to be in any stride position * Have AreCompatible handle empty shapes * Ensure IndexOutOfRangeException is thrown for invalid indexes * Ensure that the stride is set to 0 when the length of a dimension is 1, so embedded broadcasting works * Fixing Broadcasting Loop (#29) * Fixing Broadcasting Loop * fixes from pr coments * squeeze fixed * unsqueeze * set slice * more tensor fies * Ensure that minimumLinearLength is actually the minimum * Ensure the rented buffer is cleared * Fix the AreCompatible checks * Tensor finishing (#30) * stack working * more tensor tests working * fix factory create tests * only2 tests left * Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs * Update compatibility suppressions * transpose working * reverse working * Revert the unnecessary sln changes * Remove an unnecessary using --------- Co-authored-by: Tanner Gooding Co-authored-by: Michael Sharp <51342856+michaelgsharp@users.noreply.github.com> Co-authored-by: Michael Sharp --- .../ref/System.Numerics.Tensors.netcore.cs | 155 +- .../src/CompatibilitySuppressions.xml | 560 ++ .../src/Resources/Strings.resx | 9 +- .../src/System.Numerics.Tensors.csproj | 27 +- .../src/System/{ => Buffers}/NIndex.cs | 0 .../src/System/{ => Buffers}/NRange.cs | 0 .../Tensors/netcore/IReadOnlyTensor.cs | 183 +- .../Tensors/netcore/IReadOnlyTensor_1.cs | 103 + .../Numerics/Tensors/netcore/ITensor.cs | 162 +- .../Numerics/Tensors/netcore/ITensor_1.cs | 93 + .../Tensors/netcore/ReadOnlyTensorSpan.cs | 828 -- .../Tensors/netcore/ReadOnlyTensorSpan_1.cs | 524 ++ .../Tensors/netcore/Tensor.Factory.cs | 216 - .../System/Numerics/Tensors/netcore/Tensor.cs | 5907 ++++++++++++-- .../Tensors/netcore/TensorExtensions.cs | 7183 ----------------- .../Numerics/Tensors/netcore/TensorHelpers.cs | 124 - .../Tensors/netcore/TensorOperation.cs | 2615 ++++++ .../Numerics/Tensors/netcore/TensorShape.cs | 1255 ++- .../Numerics/Tensors/netcore/TensorSpan.cs | 933 +-- .../Tensors/netcore/TensorSpanHelpers.T.cs | 53 - .../Tensors/netcore/TensorSpanHelpers.cs | 306 - .../Numerics/Tensors/netcore/Tensor_1.cs | 451 ++ .../src/System/ThrowHelper.cs | 43 +- .../tests/ReadOnlyTensorSpanTests.cs | 176 +- .../tests/TensorSpanTests.cs | 560 +- .../tests/TensorTests.cs | 601 +- 26 files changed, 12027 insertions(+), 11040 deletions(-) rename src/libraries/System.Numerics.Tensors/src/System/{ => Buffers}/NIndex.cs (100%) rename src/libraries/System.Numerics.Tensors/src/System/{ => Buffers}/NRange.cs (100%) create mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs create mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor_1.cs delete mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs create mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs delete mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs delete mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs delete mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorHelpers.cs create mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorOperation.cs delete mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.T.cs delete mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs create mode 100644 src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs index 41a1c32987c353..241a65c5da5067 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs @@ -78,15 +78,15 @@ public partial interface IReadOnlyTensor : System.Collections.Generic. TSelf this[params scoped System.ReadOnlySpan ranges] { get; } new T this[params scoped System.ReadOnlySpan indexes] { get; } System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(); - System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndex); - System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan range); - System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan start); + System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndexes); + System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan ranges); + System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndexes); void CopyTo(scoped System.Numerics.Tensors.TensorSpan destination); void FlattenTo(scoped System.Span destination); ref readonly T GetPinnableReference(); - TSelf Slice(params scoped System.ReadOnlySpan startIndex); - TSelf Slice(params scoped System.ReadOnlySpan range); - TSelf Slice(params scoped System.ReadOnlySpan start); + TSelf Slice(params scoped System.ReadOnlySpan startIndexes); + TSelf Slice(params scoped System.ReadOnlySpan ranges); + TSelf Slice(params scoped System.ReadOnlySpan startIndexes); bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination); bool TryFlattenTo(scoped System.Span destination); } @@ -106,9 +106,9 @@ public partial interface ITensor : System.Collections.Generic.IEnumera new TSelf this[params scoped System.ReadOnlySpan ranges] { get; set; } new T this[params scoped System.ReadOnlySpan indexes] { get; set; } System.Numerics.Tensors.TensorSpan AsTensorSpan(); - System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan startIndex); - System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan range); - System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan start); + System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan startIndexes); + System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan ranges); + System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan startIndexes); static abstract TSelf Create(scoped System.ReadOnlySpan lengths, bool pinned = false); static abstract TSelf Create(scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned = false); static abstract TSelf CreateUninitialized(scoped System.ReadOnlySpan lengths, bool pinned = false); @@ -122,16 +122,19 @@ public readonly ref partial struct ReadOnlyTensorSpan private readonly object _dummy; private readonly int _dummyPrimitive; public ReadOnlyTensorSpan(System.Array? array) { throw null; } - public ReadOnlyTensorSpan(System.Array? array, scoped System.ReadOnlySpan startIndex, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public ReadOnlyTensorSpan(System.Array? array, scoped System.ReadOnlySpan start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public ReadOnlyTensorSpan(System.ReadOnlySpan span) { throw null; } + public ReadOnlyTensorSpan(System.ReadOnlySpan span, scoped System.ReadOnlySpan lengths) { throw null; } public ReadOnlyTensorSpan(System.ReadOnlySpan span, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } [System.CLSCompliantAttribute(false)] public unsafe ReadOnlyTensorSpan(T* data, nint dataLength) { throw null; } [System.CLSCompliantAttribute(false)] + public unsafe ReadOnlyTensorSpan(T* data, nint dataLength, scoped System.ReadOnlySpan lengths) { throw null; } + [System.CLSCompliantAttribute(false)] public unsafe ReadOnlyTensorSpan(T* data, nint dataLength, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public ReadOnlyTensorSpan(T[]? array) { throw null; } - public ReadOnlyTensorSpan(T[]? array, System.Index startIndex, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } + public ReadOnlyTensorSpan(T[]? array, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } + public ReadOnlyTensorSpan(T[]? array, scoped System.ReadOnlySpan lengths) { throw null; } public ReadOnlyTensorSpan(T[]? array, int start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public static System.Numerics.Tensors.ReadOnlyTensorSpan Empty { get { throw null; } } public nint FlattenedLength { get { throw null; } } @@ -162,18 +165,23 @@ public void CopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { } public static bool operator ==(System.Numerics.Tensors.ReadOnlyTensorSpan left, System.Numerics.Tensors.ReadOnlyTensorSpan right) { throw null; } public static implicit operator System.Numerics.Tensors.ReadOnlyTensorSpan (T[]? array) { throw null; } public static bool operator !=(System.Numerics.Tensors.ReadOnlyTensorSpan left, System.Numerics.Tensors.ReadOnlyTensorSpan right) { throw null; } - public System.Numerics.Tensors.ReadOnlyTensorSpan Slice(params scoped System.ReadOnlySpan indexes) { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan Slice(params scoped System.ReadOnlySpan startIndexes) { throw null; } public System.Numerics.Tensors.ReadOnlyTensorSpan Slice(params scoped System.ReadOnlySpan ranges) { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan Slice(params scoped System.ReadOnlySpan startIndexes) { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { throw null; } public bool TryFlattenTo(scoped System.Span destination) { throw null; } public void FlattenTo(scoped System.Span destination) { throw null; } - public ref partial struct Enumerator + public ref partial struct Enumerator : System.Collections.Generic.IEnumerator { private object _dummy; private int _dummyPrimitive; - public ref readonly T Current { get { throw null; } } + public readonly ref readonly T Current { get { throw null; } } public bool MoveNext() { throw null; } + public void Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } + readonly object? System.Collections.IEnumerator.Current { get { throw null; } } + readonly T System.Collections.Generic.IEnumerator.Current { get { throw null; } } } } [System.Diagnostics.CodeAnalysis.Experimental("SYSLIB5001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] @@ -197,8 +205,14 @@ public static partial class Tensor public static ref readonly System.Numerics.Tensors.TensorSpan AsinPi(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static System.Numerics.Tensors.Tensor Asin(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan Asin(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } - public static System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, params scoped System.ReadOnlySpan lengths) { throw null; } - public static System.Numerics.Tensors.TensorSpan AsTensorSpan(this T[]? array, params scoped System.ReadOnlySpan lengths) { throw null; } + public static System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array) { throw null; } + public static System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, scoped System.ReadOnlySpan lengths) { throw null; } + public static System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } + public static System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, int start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } + public static System.Numerics.Tensors.TensorSpan AsTensorSpan(this T[]? array) { throw null; } + public static System.Numerics.Tensors.TensorSpan AsTensorSpan(this T[]? array, scoped System.ReadOnlySpan lengths) { throw null; } + public static System.Numerics.Tensors.TensorSpan AsTensorSpan(this T[]? array, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } + public static System.Numerics.Tensors.TensorSpan AsTensorSpan(this T[]? array, int start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public static System.Numerics.Tensors.Tensor Atan2Pi(in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IFloatingPointIeee754 { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan Atan2Pi(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, scoped in System.Numerics.Tensors.ReadOnlyTensorSpan y, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IFloatingPointIeee754 { throw null; } public static System.Numerics.Tensors.Tensor Atan2Pi(in System.Numerics.Tensors.ReadOnlyTensorSpan x, T y) where T : System.Numerics.IFloatingPointIeee754 { throw null; } @@ -251,24 +265,26 @@ public static void BroadcastTo(this System.Numerics.Tensors.Tensor source, public static ref readonly System.Numerics.Tensors.TensorSpan CopySign(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, T sign, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.INumber { throw null; } public static System.Numerics.Tensors.Tensor Cosh(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IHyperbolicFunctions { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan Cosh(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IHyperbolicFunctions { throw null; } - public static System.Numerics.Tensors.Tensor CosineSimilarity(in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IRootFunctions { throw null; } - public static ref readonly System.Numerics.Tensors.TensorSpan CosineSimilarity(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, scoped in System.Numerics.Tensors.ReadOnlyTensorSpan y, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IRootFunctions { throw null; } + public static T CosineSimilarity(in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IRootFunctions { throw null; } public static System.Numerics.Tensors.Tensor CosPi(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan CosPi(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static System.Numerics.Tensors.Tensor Cos(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan Cos(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } - public static System.Numerics.Tensors.Tensor CreateAndFillGaussianNormalDistribution(System.Random random, params scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } - public static System.Numerics.Tensors.Tensor CreateAndFillGaussianNormalDistribution(params scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } - public static System.Numerics.Tensors.Tensor CreateAndFillUniformDistribution(System.Random random, params scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } - public static System.Numerics.Tensors.Tensor CreateAndFillUniformDistribution(params scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } + public static System.Numerics.Tensors.Tensor CreateAndFillGaussianNormalDistribution(System.Random random, scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } + public static System.Numerics.Tensors.Tensor CreateAndFillGaussianNormalDistribution(scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } + public static System.Numerics.Tensors.Tensor CreateAndFillUniformDistribution(System.Random random, scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } + public static System.Numerics.Tensors.Tensor CreateAndFillUniformDistribution(scoped System.ReadOnlySpan lengths) where T : System.Numerics.IFloatingPoint { throw null; } public static System.Numerics.Tensors.Tensor CreateUninitialized(scoped System.ReadOnlySpan lengths, bool pinned = false) { throw null; } public static System.Numerics.Tensors.Tensor CreateUninitialized(scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned = false) { throw null; } - public static System.Numerics.Tensors.Tensor Create(System.Collections.Generic.IEnumerable values, scoped System.ReadOnlySpan lengths, bool pinned = false) { throw null; } - public static System.Numerics.Tensors.Tensor Create(System.Collections.Generic.IEnumerable values, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned = false) { throw null; } + public static System.Numerics.Tensors.Tensor Create(System.Collections.Generic.IEnumerable enumerable, bool pinned = false) { throw null; } + public static System.Numerics.Tensors.Tensor Create(System.Collections.Generic.IEnumerable enumerable, scoped System.ReadOnlySpan lengths, bool pinned = false) { throw null; } + public static System.Numerics.Tensors.Tensor Create(System.Collections.Generic.IEnumerable enumerable, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned = false) { throw null; } public static System.Numerics.Tensors.Tensor Create(scoped System.ReadOnlySpan lengths, bool pinned = false) { throw null; } public static System.Numerics.Tensors.Tensor Create(scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned = false) { throw null; } - public static System.Numerics.Tensors.Tensor Create(T[] values, scoped System.ReadOnlySpan lengths, bool pinned = false) { throw null; } - public static System.Numerics.Tensors.Tensor Create(T[] values, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned = false) { throw null; } + public static System.Numerics.Tensors.Tensor Create(T[] array) { throw null; } + public static System.Numerics.Tensors.Tensor Create(T[] array, scoped System.ReadOnlySpan lengths) { throw null; } + public static System.Numerics.Tensors.Tensor Create(T[] array, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } + public static System.Numerics.Tensors.Tensor Create(T[] array, int start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public static System.Numerics.Tensors.Tensor DegreesToRadians(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan DegreesToRadians(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static T Distance(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, scoped in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IRootFunctions { throw null; } @@ -305,7 +321,6 @@ public static void BroadcastTo(this System.Numerics.Tensors.Tensor source, public static ref readonly System.Numerics.Tensors.TensorSpan FilteredUpdate(this in System.Numerics.Tensors.TensorSpan tensor, scoped in System.Numerics.Tensors.ReadOnlyTensorSpan filter, T value) { throw null; } public static System.Numerics.Tensors.Tensor Floor(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IFloatingPoint { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan Floor(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IFloatingPoint { throw null; } - public static nint[] GetSmallestBroadcastableLengths(System.ReadOnlySpan shape1, System.ReadOnlySpan shape2) { throw null; } public static bool GreaterThanAll(in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IComparisonOperators { throw null; } public static bool GreaterThanAll(in System.Numerics.Tensors.ReadOnlyTensorSpan x, T y) where T : System.Numerics.IComparisonOperators { throw null; } public static bool GreaterThanAll(T x, in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IComparisonOperators { throw null; } @@ -340,10 +355,10 @@ public static void BroadcastTo(this System.Numerics.Tensors.Tensor source, public static ref readonly System.Numerics.Tensors.TensorSpan Ieee754Remainder(T x, scoped in System.Numerics.Tensors.ReadOnlyTensorSpan y, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IFloatingPointIeee754 { throw null; } public static System.Numerics.Tensors.Tensor ILogB(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IFloatingPointIeee754 { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan ILogB(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IFloatingPointIeee754 { throw null; } - public static int IndexOfMaxMagnitude(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } - public static int IndexOfMax(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } - public static int IndexOfMinMagnitude(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } - public static int IndexOfMin(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } + public static nint IndexOfMaxMagnitude(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } + public static nint IndexOfMax(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } + public static nint IndexOfMinMagnitude(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } + public static nint IndexOfMin(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.INumber { throw null; } public static System.Numerics.Tensors.Tensor LeadingZeroCount(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IBinaryInteger { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan LeadingZeroCount(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IBinaryInteger { throw null; } public static bool LessThanAll(in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IComparisonOperators { throw null; } @@ -435,7 +450,7 @@ public static void BroadcastTo(this System.Numerics.Tensors.Tensor source, public static T Norm(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IRootFunctions { throw null; } public static System.Numerics.Tensors.Tensor OnesComplement(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IBitwiseOperators { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan OnesComplement(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan y, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IBitwiseOperators { throw null; } - public static System.Numerics.Tensors.Tensor PermuteDimensions(this System.Numerics.Tensors.Tensor tensor, params scoped System.ReadOnlySpan dimensions) { throw null; } + public static System.Numerics.Tensors.Tensor PermuteDimensions(this System.Numerics.Tensors.Tensor tensor, scoped System.ReadOnlySpan dimensions) { throw null; } public static System.Numerics.Tensors.Tensor PopCount(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IBinaryInteger { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan PopCount(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan y, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IBinaryInteger { throw null; } public static System.Numerics.Tensors.Tensor Pow(in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.ReadOnlyTensorSpan y) where T : System.Numerics.IPowerFunctions { throw null; } @@ -449,9 +464,9 @@ public static void BroadcastTo(this System.Numerics.Tensors.Tensor source, public static ref readonly System.Numerics.Tensors.TensorSpan RadiansToDegrees(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static System.Numerics.Tensors.Tensor Reciprocal(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IFloatingPoint { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan Reciprocal(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IFloatingPoint { throw null; } - public static System.Numerics.Tensors.ReadOnlyTensorSpan Reshape(this in System.Numerics.Tensors.ReadOnlyTensorSpan tensor, params scoped System.ReadOnlySpan lengths) { throw null; } - public static System.Numerics.Tensors.TensorSpan Reshape(this in System.Numerics.Tensors.TensorSpan tensor, params scoped System.ReadOnlySpan lengths) { throw null; } - public static System.Numerics.Tensors.Tensor Reshape(this System.Numerics.Tensors.Tensor tensor, params scoped System.ReadOnlySpan lengths) { throw null; } + public static System.Numerics.Tensors.ReadOnlyTensorSpan Reshape(this in System.Numerics.Tensors.ReadOnlyTensorSpan tensor, scoped System.ReadOnlySpan lengths) { throw null; } + public static System.Numerics.Tensors.TensorSpan Reshape(this in System.Numerics.Tensors.TensorSpan tensor, scoped System.ReadOnlySpan lengths) { throw null; } + public static System.Numerics.Tensors.Tensor Reshape(this System.Numerics.Tensors.Tensor tensor, scoped System.ReadOnlySpan lengths) { throw null; } public static void ResizeTo(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan tensor, in System.Numerics.Tensors.TensorSpan destination) { } public static void ResizeTo(scoped in System.Numerics.Tensors.TensorSpan tensor, in System.Numerics.Tensors.TensorSpan destination) { } public static void ResizeTo(scoped in System.Numerics.Tensors.Tensor tensor, in System.Numerics.Tensors.TensorSpan destination) { } @@ -515,9 +530,9 @@ public static void ResizeTo(scoped in System.Numerics.Tensors.Tensor tenso public static ref readonly System.Numerics.Tensors.TensorSpan TanPi(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static System.Numerics.Tensors.Tensor Tan(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.ITrigonometricFunctions { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan Tan(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.ITrigonometricFunctions { throw null; } - public static string ToString(this in System.Numerics.Tensors.ReadOnlyTensorSpan tensor, params scoped System.ReadOnlySpan maximumLengths) { throw null; } - public static string ToString(this in System.Numerics.Tensors.TensorSpan tensor, params scoped System.ReadOnlySpan maximumLengths) { throw null; } - public static string ToString(this System.Numerics.Tensors.Tensor tensor, params scoped System.ReadOnlySpan maximumLengths) { throw null; } + public static string ToString(this in System.Numerics.Tensors.ReadOnlyTensorSpan tensor, scoped System.ReadOnlySpan maximumLengths) { throw null; } + public static string ToString(this in System.Numerics.Tensors.TensorSpan tensor, scoped System.ReadOnlySpan maximumLengths) { throw null; } + public static string ToString(this System.Numerics.Tensors.Tensor tensor, scoped System.ReadOnlySpan maximumLengths) { throw null; } public static System.Numerics.Tensors.Tensor TrailingZeroCount(in System.Numerics.Tensors.ReadOnlyTensorSpan x) where T : System.Numerics.IBinaryInteger { throw null; } public static ref readonly System.Numerics.Tensors.TensorSpan TrailingZeroCount(scoped in System.Numerics.Tensors.ReadOnlyTensorSpan x, in System.Numerics.Tensors.TensorSpan destination) where T : System.Numerics.IBinaryInteger { throw null; } public static System.Numerics.Tensors.Tensor Transpose(System.Numerics.Tensors.Tensor tensor) { throw null; } @@ -774,16 +789,19 @@ public readonly ref partial struct TensorSpan private readonly object _dummy; private readonly int _dummyPrimitive; public TensorSpan(System.Array? array) { throw null; } - public TensorSpan(System.Array? array, scoped System.ReadOnlySpan startIndex, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public TensorSpan(System.Array? array, scoped System.ReadOnlySpan start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public TensorSpan(System.Span span) { throw null; } + public TensorSpan(System.Span span, scoped System.ReadOnlySpan lengths) { throw null; } public TensorSpan(System.Span span, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } [System.CLSCompliantAttribute(false)] public unsafe TensorSpan(T* data, nint dataLength) { throw null; } [System.CLSCompliantAttribute(false)] + public unsafe TensorSpan(T* data, nint dataLength, scoped System.ReadOnlySpan lengths) { throw null; } + [System.CLSCompliantAttribute(false)] public unsafe TensorSpan(T* data, nint dataLength, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public TensorSpan(T[]? array) { throw null; } - public TensorSpan(T[]? array, System.Index startIndex, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } + public TensorSpan(T[]? array, scoped System.ReadOnlySpan lengths) { throw null; } + public TensorSpan(T[]? array, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public TensorSpan(T[]? array, int start, scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides) { throw null; } public static System.Numerics.Tensors.TensorSpan Empty { get { throw null; } } public nint FlattenedLength { get { throw null; } } @@ -796,6 +814,10 @@ public readonly ref partial struct TensorSpan public int Rank { get { throw null; } } [System.Diagnostics.CodeAnalysis.UnscopedRefAttribute] public System.ReadOnlySpan Strides { get { throw null; } } + public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan() { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndexes) { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan ranges) { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndexes) { throw null; } public void Clear() { } public void CopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] @@ -814,20 +836,25 @@ public void FlattenTo(scoped System.Span destination) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public ref T GetPinnableReference() { throw null; } public static bool operator ==(System.Numerics.Tensors.TensorSpan left, System.Numerics.Tensors.TensorSpan right) { throw null; } - public static implicit operator System.Numerics.Tensors.ReadOnlyTensorSpan (System.Numerics.Tensors.TensorSpan span) { throw null; } + public static implicit operator System.Numerics.Tensors.ReadOnlyTensorSpan (System.Numerics.Tensors.TensorSpan tensor) { throw null; } public static implicit operator System.Numerics.Tensors.TensorSpan (T[]? array) { throw null; } public static bool operator !=(System.Numerics.Tensors.TensorSpan left, System.Numerics.Tensors.TensorSpan right) { throw null; } - public System.Numerics.Tensors.TensorSpan Slice(params scoped System.ReadOnlySpan indexes) { throw null; } + public System.Numerics.Tensors.TensorSpan Slice(params scoped System.ReadOnlySpan startIndexes) { throw null; } public System.Numerics.Tensors.TensorSpan Slice(params scoped System.ReadOnlySpan ranges) { throw null; } + public System.Numerics.Tensors.TensorSpan Slice(params scoped System.ReadOnlySpan startIndexes) { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { throw null; } public bool TryFlattenTo(scoped System.Span destination) { throw null; } - public ref partial struct Enumerator + public ref partial struct Enumerator : System.Collections.Generic.IEnumerator { private object _dummy; private int _dummyPrimitive; - public ref T Current { get { throw null; } } + public readonly ref T Current { get { throw null; } } public bool MoveNext() { throw null; } + public void Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } + readonly object? System.Collections.IEnumerator.Current { get { throw null; } } + readonly T System.Collections.Generic.IEnumerator.Current { get { throw null; } } } } [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5001", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] @@ -838,7 +865,6 @@ internal Tensor() { } public nint FlattenedLength { get { throw null; } } public bool IsEmpty { get { throw null; } } public bool IsPinned { get { throw null; } } - public System.Numerics.Tensors.Tensor this[System.Numerics.Tensors.Tensor filter] { get { throw null; } } public ref T this[params scoped System.ReadOnlySpan indexes] { get { throw null; } } public System.Numerics.Tensors.Tensor this[params scoped System.ReadOnlySpan ranges] { get { throw null; } set { } } public ref T this[params scoped System.ReadOnlySpan indexes] { get { throw null; } } @@ -858,39 +884,50 @@ internal Tensor() { } T System.Numerics.Tensors.ITensor, T>.this[params scoped System.ReadOnlySpan indexes] { get { throw null; } set { } } T System.Numerics.Tensors.ITensor, T>.this[params scoped System.ReadOnlySpan indexes] { get { throw null; } set { } } public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan() { throw null; } - public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndex) { throw null; } - public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan start) { throw null; } - public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan start) { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndexes) { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan ranges) { throw null; } + public System.Numerics.Tensors.ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan startIndexes) { throw null; } public System.Numerics.Tensors.TensorSpan AsTensorSpan() { throw null; } - public System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan startIndex) { throw null; } - public System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan start) { throw null; } - public System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan start) { throw null; } + public System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan startIndexes) { throw null; } + public System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan ranges) { throw null; } + public System.Numerics.Tensors.TensorSpan AsTensorSpan(params scoped System.ReadOnlySpan startIndexes) { throw null; } public void Clear() { } public void CopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { } - public void Fill(object value) { } public void Fill(T value) { } public void FlattenTo(scoped System.Span destination) { } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } + public Enumerator GetEnumerator() { throw null; } public override int GetHashCode() { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public ref T GetPinnableReference() { throw null; } public System.Buffers.MemoryHandle GetPinnedHandle() { throw null; } - public static implicit operator System.Numerics.Tensors.ReadOnlyTensorSpan (System.Numerics.Tensors.Tensor value) { throw null; } - public static implicit operator System.Numerics.Tensors.TensorSpan (System.Numerics.Tensors.Tensor value) { throw null; } + public static implicit operator System.Numerics.Tensors.ReadOnlyTensorSpan (System.Numerics.Tensors.Tensor tensor) { throw null; } + public static implicit operator System.Numerics.Tensors.TensorSpan (System.Numerics.Tensors.Tensor tensor) { throw null; } public static implicit operator System.Numerics.Tensors.Tensor (T[] array) { throw null; } - public System.Numerics.Tensors.Tensor Slice(params scoped System.ReadOnlySpan startIndex) { throw null; } - public System.Numerics.Tensors.Tensor Slice(params scoped System.ReadOnlySpan start) { throw null; } - public System.Numerics.Tensors.Tensor Slice(params scoped System.ReadOnlySpan start) { throw null; } + public System.Numerics.Tensors.Tensor Slice(params scoped System.ReadOnlySpan startIndexes) { throw null; } + public System.Numerics.Tensors.Tensor Slice(params scoped System.ReadOnlySpan ranges) { throw null; } + public System.Numerics.Tensors.Tensor Slice(params scoped System.ReadOnlySpan startIndexes) { throw null; } System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] ref readonly T System.Numerics.Tensors.IReadOnlyTensor, T>.GetPinnableReference() { throw null; } + void System.Numerics.Tensors.ITensor.Fill(object value) { } static System.Numerics.Tensors.Tensor System.Numerics.Tensors.ITensor, T>.Create(scoped System.ReadOnlySpan lengths, bool pinned) { throw null; } static System.Numerics.Tensors.Tensor System.Numerics.Tensors.ITensor, T>.Create(scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned) { throw null; } static System.Numerics.Tensors.Tensor System.Numerics.Tensors.ITensor, T>.CreateUninitialized(scoped System.ReadOnlySpan lengths, bool pinned) { throw null; } static System.Numerics.Tensors.Tensor System.Numerics.Tensors.ITensor, T>.CreateUninitialized(scoped System.ReadOnlySpan lengths, scoped System.ReadOnlySpan strides, bool pinned) { throw null; } - public string ToString(params scoped System.ReadOnlySpan maximumLengths) { throw null; } + public string ToString(scoped System.ReadOnlySpan maximumLengths) { throw null; } public bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { throw null; } public bool TryFlattenTo(scoped System.Span destination) { throw null; } + public partial struct Enumerator : System.Collections.Generic.IEnumerator + { + private object _dummy; + private int _dummyPrimitive; + public readonly ref T Current { get { throw null; } } + public bool MoveNext() { throw null; } + public void Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } + readonly object? System.Collections.IEnumerator.Current { get { throw null; } } + readonly T System.Collections.Generic.IEnumerator.Current { get { throw null; } } + } } } diff --git a/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml b/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml index 06a5854b5aba3c..9fda67ddf4a768 100644 --- a/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml +++ b/src/libraries/System.Numerics.Tensors/src/CompatibilitySuppressions.xml @@ -127,6 +127,300 @@ lib/netstandard2.0/System.Numerics.Tensors.dll true + + CP0002 + M:System.Numerics.Tensors.ReadOnlyTensorSpan`1.#ctor(`0[],System.Index,System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.ReadOnlyTensorSpan`1.#ctor(System.Array,System.ReadOnlySpan{System.Buffers.NIndex},System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.CosineSimilarity``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,System.Numerics.Tensors.TensorSpan{``0}@) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.CosineSimilarity``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.Create``1(``0[],System.ReadOnlySpan{System.IntPtr},System.Boolean) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.Create``1(``0[],System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr},System.Boolean) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.GetSmallestBroadcastableLengths(System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMax``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMaxMagnitude``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMin``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMinMagnitude``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor`1.get_Item(System.Numerics.Tensors.Tensor{System.Boolean}) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor`1.GetEnumerator + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.TensorSpan`1.#ctor(`0[],System.Index,System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.TensorSpan`1.#ctor(System.Array,System.ReadOnlySpan{System.Buffers.NIndex},System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.ReadOnlyTensorSpan`1.#ctor(`0[],System.Index,System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.ReadOnlyTensorSpan`1.#ctor(System.Array,System.ReadOnlySpan{System.Buffers.NIndex},System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.CosineSimilarity``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,System.Numerics.Tensors.TensorSpan{``0}@) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.CosineSimilarity``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.Create``1(``0[],System.ReadOnlySpan{System.IntPtr},System.Boolean) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.Create``1(``0[],System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr},System.Boolean) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.GetSmallestBroadcastableLengths(System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMax``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMaxMagnitude``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMin``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor.IndexOfMinMagnitude``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor`1.get_Item(System.Numerics.Tensors.Tensor{System.Boolean}) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.Tensor`1.GetEnumerator + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.TensorSpan`1.#ctor(`0[],System.Index,System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0002 + M:System.Numerics.Tensors.TensorSpan`1.#ctor(System.Array,System.ReadOnlySpan{System.Buffers.NIndex},System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr}) + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.Slice(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.Slice(System.ReadOnlySpan{System.IntPtr})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ITensor`2.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ITensor`2.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ITensor`2.AsTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ReadOnlyTensorSpan`1.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor.Create``1(System.Collections.Generic.IEnumerable{``0},System.ReadOnlySpan{System.IntPtr},System.Boolean)$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor.Create``1(System.Collections.Generic.IEnumerable{``0},System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr},System.Boolean)$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + CP0017 M:System.Numerics.Tensors.Tensor.GreaterThanOrEqualAll``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,``0)$0 @@ -190,6 +484,181 @@ lib/net8.0/System.Numerics.Tensors.dll true + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.op_Implicit(System.Numerics.Tensors.Tensor{`0})~System.Numerics.Tensors.ReadOnlyTensorSpan{`0}$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.op_Implicit(System.Numerics.Tensors.Tensor{`0})~System.Numerics.Tensors.TensorSpan{`0}$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.Slice(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.Slice(System.ReadOnlySpan{System.IntPtr})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.TensorSpan`1.op_Implicit(System.Numerics.Tensors.TensorSpan{`0})~System.Numerics.Tensors.ReadOnlyTensorSpan{`0}$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.TensorSpan`1.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net8.0/System.Numerics.Tensors.dll + lib/net8.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.Slice(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.IReadOnlyTensor`2.Slice(System.ReadOnlySpan{System.IntPtr})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ITensor`2.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ITensor`2.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ITensor`2.AsTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.ReadOnlyTensorSpan`1.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor.Create``1(System.Collections.Generic.IEnumerable{``0},System.ReadOnlySpan{System.IntPtr},System.Boolean)$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor.Create``1(System.Collections.Generic.IEnumerable{``0},System.ReadOnlySpan{System.IntPtr},System.ReadOnlySpan{System.IntPtr},System.Boolean)$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + CP0017 M:System.Numerics.Tensors.Tensor.GreaterThanOrEqualAll``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@,``0)$0 @@ -253,6 +722,97 @@ lib/net9.0/System.Numerics.Tensors.dll true + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsReadOnlyTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsTensorSpan(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.AsTensorSpan(System.ReadOnlySpan{System.IntPtr})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.op_Implicit(System.Numerics.Tensors.Tensor{`0})~System.Numerics.Tensors.ReadOnlyTensorSpan{`0}$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.op_Implicit(System.Numerics.Tensors.Tensor{`0})~System.Numerics.Tensors.TensorSpan{`0}$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.Slice(System.ReadOnlySpan{System.Buffers.NRange})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.Tensor`1.Slice(System.ReadOnlySpan{System.IntPtr})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.TensorSpan`1.op_Implicit(System.Numerics.Tensors.TensorSpan{`0})~System.Numerics.Tensors.ReadOnlyTensorSpan{`0}$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + + + CP0017 + M:System.Numerics.Tensors.TensorSpan`1.Slice(System.ReadOnlySpan{System.Buffers.NIndex})$0 + lib/net9.0/System.Numerics.Tensors.dll + lib/net9.0/System.Numerics.Tensors.dll + true + CP0021 M:System.Numerics.Tensors.Tensor.Average``1(System.Numerics.Tensors.ReadOnlyTensorSpan{``0}@)``0:T:System.Numerics.INumberBase{``0} diff --git a/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx b/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx index 1039f4e0e286f0..208b9802175091 100644 --- a/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx +++ b/src/libraries/System.Numerics.Tensors/src/Resources/Strings.resx @@ -201,8 +201,8 @@ When no ranges are specified the values tensor must be equal in size as the input tensor. - - Lengths are not broadcast compatible. + + Lengths are not compatible with each other. The number of splits must perfectly divide the dimension. @@ -240,4 +240,7 @@ Function does not accept floating point Not-a-Number values. - + + The provided length is non zero while the data reference is null. + + \ No newline at end of file diff --git a/src/libraries/System.Numerics.Tensors/src/System.Numerics.Tensors.csproj b/src/libraries/System.Numerics.Tensors/src/System.Numerics.Tensors.csproj index 807aaf15ff873d..7202c69d6ab762 100644 --- a/src/libraries/System.Numerics.Tensors/src/System.Numerics.Tensors.csproj +++ b/src/libraries/System.Numerics.Tensors/src/System.Numerics.Tensors.csproj @@ -19,20 +19,8 @@ - - - - - - - - - - - - - - + + @@ -44,6 +32,14 @@ + + + + + + + + @@ -170,6 +166,9 @@ + + + diff --git a/src/libraries/System.Numerics.Tensors/src/System/NIndex.cs b/src/libraries/System.Numerics.Tensors/src/System/Buffers/NIndex.cs similarity index 100% rename from src/libraries/System.Numerics.Tensors/src/System/NIndex.cs rename to src/libraries/System.Numerics.Tensors/src/System/Buffers/NIndex.cs diff --git a/src/libraries/System.Numerics.Tensors/src/System/NRange.cs b/src/libraries/System.Numerics.Tensors/src/System/Buffers/NRange.cs similarity index 100% rename from src/libraries/System.Numerics.Tensors/src/System/NRange.cs rename to src/libraries/System.Numerics.Tensors/src/System/Buffers/NRange.cs diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs index ce52ab8c279b91..9a90a17cb1640c 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor.cs @@ -2,181 +2,50 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace System.Numerics.Tensors { - - /// - /// Represents a read-only tensor. - /// + /// Represents a read-only tensor. [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] public interface IReadOnlyTensor { - /// - /// Gets a value that indicates whether the collection is currently empty. - /// + /// Gets the specified element of the tensor. + /// The index of the element for which to get. + /// The element that exists at . + /// + /// Thrown when one of the following conditions is met: + /// * does not contain elements + /// * contains an element that is negative or greater than or equal to the corresponding dimension length + /// + object? this[params scoped ReadOnlySpan indexes] { get; } + + /// + object? this[params scoped ReadOnlySpan indexes] { get; } + + /// Gets the total number of items in the tensor. + nint FlattenedLength { get; } + + /// Gets a value indicating whether this tensor is empty. + /// if this tensor is empty; otherwise, . bool IsEmpty { get; } - /// - /// Gets a value that indicates whether the underlying buffer is pinned. - /// + /// Gets a value that indicates whether the underlying buffer is pinned. bool IsPinned { get; } - /// - /// Gets the number of elements in the tensor. - /// - nint FlattenedLength { get; } - - /// - /// Gets the number of dimensions in the tensor. - /// - int Rank { get; } - - /// - /// Gets the length of each dimension in the tensor. - /// + /// Gets the length of each dimension in the tensor. [UnscopedRef] ReadOnlySpan Lengths { get; } - /// - /// Gets the stride of each dimension in the tensor. - /// + /// Gets the rank, or number of dimensions, in the tensor. + int Rank { get; } + + /// Gets the stride of each dimension in the tensor. [UnscopedRef] ReadOnlySpan Strides { get; } - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to be used. - object this[params scoped ReadOnlySpan indexes] { get; } - - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to be used. - object this[params scoped ReadOnlySpan indexes] { get; } - - /// - /// Pins and gets a to the backing memory. - /// + /// Pins and gets a to the backing memory. /// MemoryHandle GetPinnedHandle(); } - - /// - /// Represents a read-only tensor. - /// - /// The type that implements this interface. - /// The element type. - [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - public interface IReadOnlyTensor : IReadOnlyTensor, IEnumerable - where TSelf : IReadOnlyTensor - { - /// - /// Gets an empty tensor. - /// - static abstract TSelf? Empty { get; } - - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to be used. - new T this[params scoped ReadOnlySpan indexes] { get; } - - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to be used. - new T this[params scoped ReadOnlySpan indexes] { get; } - - /// - /// Gets the values at the specified ranges. - /// - /// The ranges to be used. - TSelf this[params scoped ReadOnlySpan ranges] { get; } - - /// - /// Creates a read-only tensor span for the entire underlying buffer. - /// - /// The converted . - ReadOnlyTensorSpan AsReadOnlyTensorSpan(); - - /// - /// Creates a read-only tensor span for the specified start indexes. - /// - /// The start locations to be used. - /// The converted . - ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan start); - - /// - /// Creates a read-only tensor span for the specified start indexes. - /// - /// The started indexes to be used. - /// The converted . - ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndex); - - /// - /// Creates a read-only tensor span for the specified ranges. - /// - /// The ranges to be used. - /// The converted . - ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan range); - - /// - /// Copies the tensor to the specified destination. The destination tensor must be equal to or larger than the source tensor. - /// - /// The destination span where the data should be copied to. - void CopyTo(scoped TensorSpan destination); - - /// - /// Flattens the tensor to the specified destination. The destination span must be equal to or larger than the number of elements in the source tensor. - /// - /// The destination span where the data should be flattened to. - void FlattenTo(scoped Span destination); - - /// - /// Returns a reference to the 0th element of the tensor. If the tensor is empty, returns . - /// - /// - /// This method can be used for pinning and is required to support the use of the tensor within a fixed statement. - /// - ref readonly T GetPinnableReference(); - - /// - /// Slices the tensor using the specified start indexes. - /// - /// The start locations to be used. - /// The sliced tensor. - TSelf Slice(params scoped ReadOnlySpan start); - - /// - /// Slices the tensor using the specified start indexes. - /// - /// The start indexes to be used. - /// The sliced tensor. - TSelf Slice(params scoped ReadOnlySpan startIndex); - - /// - /// Slices the tensor using the specified ranges. - /// - /// The ranges to be used. - /// The sliced tensor. - TSelf Slice(params scoped ReadOnlySpan range); - - /// - /// Tries to copy the tensor to the specified destination. The destination tensor must be equal to or larger than the source tensor. - /// - /// The destination span where the data should be copied to. - /// if the copy succeeded, otherwise. - bool TryCopyTo(scoped TensorSpan destination); - - /// - /// Tries to flatten the tensor to the specified destination. The destination span must be equal to or larger than the number of elements in the source tensor. - /// - /// The destination span where the data should be flattened to. - /// if the flatten succeeded, otherwise. - bool TryFlattenTo(scoped Span destination); - } } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs new file mode 100644 index 00000000000000..022140ed33aa72 --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/IReadOnlyTensor_1.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Numerics.Tensors +{ + /// Represents a read-only tensor. + /// The type that implements this interface. + /// The element type. + [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public interface IReadOnlyTensor : IReadOnlyTensor, IEnumerable + where TSelf : IReadOnlyTensor + { + /// Gets an empty tensor. + static abstract TSelf Empty { get; } + + /// Gets a reference to the specified element of the tensor. + /// The index of the element for which to get a reference. + /// A reference to the element that exists at . + /// + /// Thrown when one of the following conditions is met: + /// * does not contain elements + /// * contains an element that is negative or greater than or equal to the corresponding dimension length + /// + new ref readonly T this[params scoped ReadOnlySpan indexes] { get; } + + /// + new ref readonly T this[params scoped ReadOnlySpan indexes] { get; } + + /// + TSelf this[params scoped ReadOnlySpan ranges] { get; } + + /// Creates a new readonly tensor span over the tensor. + /// The readonly tensor span representation of the tensor. + ReadOnlyTensorSpan AsReadOnlyTensorSpan(); + + /// Creates a new readonly tensor span over a portion of the tensor starting at a specified position to the end of the tensor. + /// The initial indexes from which the tensor will be converted. + /// The readonly tensor span representation of the tensor. + ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndexes); + + /// + ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndexes); + + /// Creates a new readonly tensor span over a portion of the tensor defined by the specified range. + /// The ranges of the tensor to convert. + /// The readonly tensor span representation of the tensor. + ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan ranges); + + /// Copies the contents of the tensor into a destination tensor span. + /// The destination tensor span. + /// is shorter than the source tensor. + /// This method copies all of the source tensor to even if they overlap. + void CopyTo(scoped in TensorSpan destination); + + /// Flattens the contents of the tensor into a destination span. + /// The destination span. + /// is shorter than the source tensor. + /// This method copies all of the source tensor to even if they overlap. + void FlattenTo(scoped Span destination); + + /// Returns a reference to an object of type that can be used for pinning. + /// A reference to the element of the tensor at index 0, or null if the tensor is empty. + /// This method is intended to support .NET compilers and is not intended to be called by user code. + ref readonly T GetPinnableReference(); + + /// Forms a slice out of the current tensor that begins at a specified index. + /// The indexes at which to begin the slice. + /// A tensor that consists of all elements of the current tensor from to the end of the tensor. + /// is greater than the number of items in the tensor. + TSelf Slice(params scoped ReadOnlySpan startIndexes); + + /// + TSelf Slice(params scoped ReadOnlySpan startIndexes); + + /// Gets a slice out of the current tensor that contains a specified range. + /// The range of which to slice. + /// A tensor that consists of all elements of the current tensor in . + /// is larger than the tensor. + TSelf Slice(params scoped ReadOnlySpan ranges); + + /// Attempts to copy the contents of this tensor into a destination tensor span and returns a value to indicate whether or not the operation succeeded. + /// The target of the copy operation. + /// if the copy operation succeeded; otherwise, false. + /// + /// If the source and overlap, the entirety of the source is handled as if it was copied to a temporary location before it is copied to . + /// If the length is shorter than the source, no items are copied and the method returns false. + /// + bool TryCopyTo(scoped in TensorSpan destination); + + /// Attempts to flatten the contents of this tensor into a destination span and returns a value to indicate whether or not the operation succeeded. + /// The target of the copy operation. + /// if the copy operation succeeded; otherwise, false. + /// + /// If the source and overlap, the entirety of the source is handled as if it was flattened to a temporary location before it is copied to . + /// If the length is shorter than the source, no items are copied and the method returns false. + /// + bool TryFlattenTo(scoped Span destination); + } +} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor.cs index 4a1cbd2b1bf800..f1adace43cb916 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor.cs @@ -6,156 +6,32 @@ namespace System.Numerics.Tensors { - /// - /// Represents a tensor. - /// + /// Represents a tensor. [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] public interface ITensor : IReadOnlyTensor { - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to be used. - new object this[params scoped ReadOnlySpan indexes] { get; set; } - - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to be used. - new object this[params scoped ReadOnlySpan indexes] { get; set; } - - /// - /// Gets a value that indicates whether the collection is read-only. - /// + /// Gets or sets the specified element of the tensor. + /// The index of the element for which to get. + /// The element that exists at . + /// + /// Thrown when one of the following conditions is met: + /// * does not contain elements + /// * contains an element that is negative or greater than or equal to the corresponding dimension length + /// + new object? this[params scoped ReadOnlySpan indexes] { get; set; } + + /// + new object? this[params scoped ReadOnlySpan indexes] { get; set; } + + /// Gets a value that indicates whether the tensor is read-only. bool IsReadOnly { get; } - /// - /// Clears the tensor. - /// + /// Clears the contents of the tensor span. + /// This method sets the items in the tensor span to their default values. It does not remove items from the tensor span. void Clear(); - /// - /// Fills the contents of this tensor with the given value. - /// + /// Fills the elements of this tensor with a specified value. + /// The value to assign to each element of the tensor. void Fill(object value); } - - /// - /// Represents a tensor. - /// - /// The type that implements this interface. - /// The element type. - [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - public interface ITensor : ITensor, IReadOnlyTensor - where TSelf : ITensor - { - // TODO: Determine if we can implement `IEqualityOperators`. - // It looks like C#/.NET currently hits limitations here as it believes TSelf and T could be the same type - // Ideally we could annotate it such that they cannot be the same type and no conflicts would exist - - /// - /// Creates a new tensor with the specified lengths. - /// - /// The lengths of each dimension. - /// to pin the underlying buffer. The default is . - /// - /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. - /// The underlying buffer is initialized to default values. - /// - static abstract TSelf Create(scoped ReadOnlySpan lengths, bool pinned = false); - - /// - /// Creates a new tensor with the specified lengths and strides. - /// - /// The lengths of each dimension. - /// The strides of each dimension. - /// to pin the underlying buffer. The default is . - /// - /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. - /// The underlying buffer is initialized to default values. - /// - static abstract TSelf Create(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false); - - /// - /// Creates a new tensor with the specified lengths and strides. - /// - /// The lengths of each dimension. - /// to pin the underlying buffer. The default is . - /// - /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. - /// The underlying buffer is not initialized. - /// - static abstract TSelf CreateUninitialized(scoped ReadOnlySpan lengths, bool pinned = false); - - /// - /// Creates a new tensor with the specified lengths and strides. If is true the underlying buffer is - /// created permanently pinned, otherwise the underlying buffer is not pinned. The underlying buffer is not initialized. - /// - /// The lengths of each dimension. - /// The strides of each dimension. - /// to pin the underlying buffer. The default is . - /// - /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. - /// The underlying buffer is not initialized. - /// - static abstract TSelf CreateUninitialized(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false); - - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to use. - new T this[params scoped ReadOnlySpan indexes] { get; set; } - - /// - /// Gets the value at the specified indexes. - /// - /// The indexes to use. - new T this[params scoped ReadOnlySpan indexes] { get; set; } - - /// - /// Gets the values at the specified ranges. - /// - /// The ranges to be used. - new TSelf this[params scoped ReadOnlySpan ranges] { get; set; } - - /// - /// Creates a tensor span for the entire underlying buffer. - /// - /// The converted . - TensorSpan AsTensorSpan(); - - /// - /// Creates a tensor span for the specified start indexes. - /// - /// The start locations to be used. - /// The converted . - TensorSpan AsTensorSpan(params scoped ReadOnlySpan start); - - /// - /// Creates a tensor span for the specified start indexes. - /// - /// The start indexes to be used. - /// The converted . - TensorSpan AsTensorSpan(params scoped ReadOnlySpan startIndex); - - /// - /// Creates a tensor span for the specified ranges. - /// - /// The ranges to be used. - /// The converted . - TensorSpan AsTensorSpan(params scoped ReadOnlySpan range); - - /// - /// Fills the contents of this tensor with the given value. - /// - void Fill(T value); - - /// - /// Returns a reference to the 0th element of the tensor. If the tensor is empty, returns . - /// - /// - /// This method can be used for pinning and is required to support the use of the tensor within a fixed statement. - /// - new ref T GetPinnableReference(); - } } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor_1.cs new file mode 100644 index 00000000000000..8be1445275e3fb --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ITensor_1.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics.CodeAnalysis; + +namespace System.Numerics.Tensors +{ + /// Represents a tensor. + /// The type that implements this interface. + /// The element type. + [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public interface ITensor : ITensor, IReadOnlyTensor + where TSelf : ITensor + { + // TODO: Determine if we can implement `IEqualityOperators`. + // It looks like C#/.NET currently hits limitations here as it believes TSelf and T could be the same type + // Ideally we could annotate it such that they cannot be the same type and no conflicts would exist + + /// Creates a new tensor with the specified lengths. + /// The lengths of each dimension. + /// to pin the underlying buffer. The default is . + /// + /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. + /// The underlying buffer is initialized to default values. + /// + static abstract TSelf Create(scoped ReadOnlySpan lengths, bool pinned = false); + + /// Creates a new tensor with the specified lengths and strides. + /// The lengths of each dimension. + /// The strides of each dimension. + /// to pin the underlying buffer. The default is . + /// + /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. + /// The underlying buffer is initialized to default values. + /// + static abstract TSelf Create(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false); + + /// Creates a new tensor with the specified lengths and strides. + /// The lengths of each dimension. + /// to pin the underlying buffer. The default is . + /// + /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. + /// The underlying buffer is not initialized. + /// + static abstract TSelf CreateUninitialized(scoped ReadOnlySpan lengths, bool pinned = false); + + /// Creates a new tensor with the specified lengths and strides. If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. The underlying buffer is not initialized. + /// The lengths of each dimension. + /// The strides of each dimension. + /// to pin the underlying buffer. The default is . + /// + /// If is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. + /// The underlying buffer is not initialized. + /// + static abstract TSelf CreateUninitialized(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false); + + /// + new ref T this[params scoped ReadOnlySpan indexes] { get; } + + /// + new ref T this[params scoped ReadOnlySpan indexes] { get; } + + /// Gets or sets a slice out of the current tensor that contains a specified range. + /// The range of which to slice. + /// A tensor that consists of all elements of the current tensor in . + /// is larger than the tensor. + new TSelf this[params scoped ReadOnlySpan ranges] { get; set; } + + /// Creates a new tensor span over the tensor. + /// The tensor span representation of the tensor. + TensorSpan AsTensorSpan(); + + /// Creates a new tensor span over a portion of the tensor starting at a specified position to the end of the tensor. + /// The initial indexes from which the tensor will be converted. + /// The tensor span representation of the tensor. + TensorSpan AsTensorSpan(params scoped ReadOnlySpan startIndexes); + + /// + TensorSpan AsTensorSpan(params scoped ReadOnlySpan startIndexes); + + /// Creates a new tensor span over a portion of the tensor defined by the specified range. + /// The ranges of the tensor to convert. + /// The tensor span representation of the tensor. + TensorSpan AsTensorSpan(params scoped ReadOnlySpan ranges); + + /// + void Fill(T value); + + /// + new ref T GetPinnableReference(); + } +} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs deleted file mode 100644 index c4bdb876dd627d..00000000000000 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs +++ /dev/null @@ -1,828 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; -using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; - -#pragma warning disable 0809 //warning CS0809: Obsolete member 'ReadOnlyTensorSpan.Equals(object)' overrides non-obsolete member 'object.Equals(object)' - -namespace System.Numerics.Tensors -{ - /// - /// Represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed - /// or native memory, or to memory allocated on the stack. It is type-safe and memory-safe. - /// - [DebuggerTypeProxy(typeof(TensorSpanDebugView<>))] - [DebuggerDisplay("{ToString(),raw}")] - [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - public readonly ref struct ReadOnlyTensorSpan - { - /// A byref or a native ptr. - internal readonly ref T _reference; - internal readonly TensorShape _shape; - - - /// - /// Creates a new span over the entirety of the target array. - /// - /// The target array. - /// Returns default when is null. - /// is covariant and its type is not exactly T[]. - public ReadOnlyTensorSpan(T[]? array) : this(array, 0, [array?.Length ?? 0], []) - { - } - - /// - /// Creates a new span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// - /// The target array. - /// The index at which to begin the span. - /// The lengths of the dimensions. If default is provided, it's assumed to have one dimension with a length equal to the length of the data. - /// The strides of each dimension. If default or span of length 0 is provided, then strides will be automatically calculated. - /// Returns default when is null. - /// is covariant and its type is not exactly T[]. - /// The specified or end index is not in the range (<0 or >FlattenedLength). - /// - public ReadOnlyTensorSpan(T[]? array, Index startIndex, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) - : this(array, startIndex.GetOffset(array?.Length ?? 0), lengths, strides) - { - } - - /// - /// Creates a new span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// - /// The target array. - /// The index at which to begin the span. - /// The lengths of the dimensions. If default is provided, it's assumed to have one dimension with a length equal to the length of the data. - /// The strides of each dimension. If default or span of length 0 is provided, then strides will be automatically calculated. - /// Returns default when is null. - /// is covariant and its type is not exactly T[]. - /// - /// Thrown when the specified or end index is not in the range (<0 or >FlattenedLength). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyTensorSpan(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) - { - if (lengths.IsEmpty && array != null) - lengths = [array.Length]; - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - if (array == null) - { - if (start != 0 || linearLength != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - this = default; - return; // returns default - } - - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException(); - - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - - if (Environment.Is64BitProcess) - { - // See comment in Span.Slice for how this works. - if ((ulong)(uint)start + (ulong)(uint)maxElements >= (ulong)(uint)array.Length && array.Length != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - } - else - { - if (((uint)start > (uint)array.Length || (uint)maxElements >= (uint)(array.Length - start)) && array.Length != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - } - - _shape = new TensorShape(array.Length - start, lengths, strides); - _reference = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (nint)(uint)start /* force zero-extension */); - } - - /// - /// Creates a new over the provided . The new will - /// have a rank of 1 and a length equal to the length of the provided . - /// - /// The target span. - public ReadOnlyTensorSpan(ReadOnlySpan span) : this(span, [span.Length], []) { } - - /// - /// Creates a new over the provided using the specified lengths and strides. - /// - /// The target span. - /// The lengths of each dimension. - /// The strides for each dimension. The strides will be automatically calculated if not provided. - public ReadOnlyTensorSpan(ReadOnlySpan span, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) - { - if (lengths.IsEmpty) - lengths = [span.Length]; - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (span.IsEmpty ? maxElements != 0 : maxElements >= span.Length) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - - _shape = new TensorShape(span.Length, lengths, strides); - _reference = ref MemoryMarshal.GetReference(span); - } - - /// - /// Creates a new over the provided . The new will - /// have a rank of 1 and a length equal to the length of the provided . - /// - /// The target array. - public ReadOnlyTensorSpan(Array? array) : - this(array, - ReadOnlySpan.Empty, - array == null ? - [0] : - TensorSpanHelpers.FillLengths(array.Rank <= TensorShape.MaxInlineRank ? - stackalloc nint[array.Rank] : - new nint[array.Rank], array), - []) - { - } - - /// - /// Creates a new over the provided using the specified start offsets, lengths, and strides. - /// - /// The target array. - /// The starting offset for each dimension. - /// The lengths of each dimension. - /// The strides for each dimension. This strides will be automatically calculated if not provided. - public ReadOnlyTensorSpan(Array? array, scoped ReadOnlySpan start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) - { - if (lengths.IsEmpty && array != null) - { - lengths = TensorSpanHelpers.FillLengths( - array.Rank <= TensorShape.MaxInlineRank ? stackalloc nint[array.Rank] : new nint[array.Rank], - array); - } - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - if (array == null) - { - if (!start.IsEmpty || linearLength != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - this = default; - return; // returns default - } - if (array.GetType().GetElementType() != typeof(T)) - ThrowHelper.ThrowArrayTypeMismatchException(); - - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - - nint startOffset = TensorSpanHelpers.ComputeStartOffsetSystemArray(array, start); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (Environment.Is64BitProcess) - { - // See comment in Span.Slice for how this works. - if ((ulong)(uint)startOffset + (ulong)(uint)maxElements >= (ulong)(uint)array.Length && array.Length != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - else - { - if (((uint)startOffset > (uint)array.Length || (uint)maxElements >= (uint)(array.Length - startOffset)) && array.Length != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - - _shape = new TensorShape(array.Length - startOffset, lengths, strides); - _reference = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), (nint)(uint)startOffset /* force zero-extension */); - } - - /// - /// Creates a new over the provided using the specified start offsets, lengths, and strides. - /// - /// The target array. - /// The starting offset for each dimension. - /// The lengths of each dimension. - /// The strides for each dimension. The strides will be automatically calculated if not provided. - public ReadOnlyTensorSpan(Array? array, scoped ReadOnlySpan startIndex, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) - { - if (lengths.IsEmpty && array != null) - { - lengths = TensorSpanHelpers.FillLengths( - array.Rank <= TensorShape.MaxInlineRank ? stackalloc nint[array.Rank] : new nint[array.Rank], - array); - } - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - if (array == null) - { - if (!startIndex.IsEmpty || linearLength != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - this = default; - return; // returns default - } - if (array.GetType().GetElementType() != typeof(T)) - ThrowHelper.ThrowArrayTypeMismatchException(); - - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - - nint startOffset = TensorSpanHelpers.ComputeStartOffsetSystemArray(array, startIndex); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (Environment.Is64BitProcess) - { - // See comment in Span.Slice for how this works. - if ((ulong)(uint)startOffset + (ulong)(uint)maxElements > (ulong)(uint)array.Length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - else - { - if ((uint)startOffset > (uint)array.Length || (uint)maxElements >= (uint)(array.Length - startOffset)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - - _shape = new TensorShape(array.Length - startOffset, lengths, strides); - _reference = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), (nint)(uint)startOffset /* force zero-extension */); - } - - /// - /// Creates a new span over the target unmanaged buffer. - /// - /// An unmanaged data that points to memory. - /// The number of elements the unmanaged memory can hold. - /// - /// This constructor is quite dangerous, because the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// - [CLSCompliant(false)] - public unsafe ReadOnlyTensorSpan(T* data, nint dataLength) : this(data, dataLength, [dataLength], []) { } - - /// - /// Creates a new span over the target unmanaged buffer. - /// - /// An unmanaged data that points to memory. - /// The number of elements the unmanaged memory can hold. - /// The lengths of the dimensions. If default is provided, it's assumed to have one dimension with a length equal to the length of the data. - /// The lengths of the strides. If nothing is provided, it figures out the default stride configuration. - /// - /// is a reference type or contains pointers and hence cannot be stored in unmanaged memory. - /// - /// - /// The specified length is negative. - /// - /// - /// This constructor is quite dangerous, because the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// - [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ReadOnlyTensorSpan(T* data, nint dataLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) - { - if (dataLength < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - - if (lengths.IsEmpty) - lengths = [dataLength]; - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (maxElements >= dataLength && dataLength != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - - _shape = new TensorShape(dataLength, lengths, strides); - _reference = ref *data; - } - - // Constructor for internal use only. It is not safe to expose publicly. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlyTensorSpan(ref T reference, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, nint memoryLength) - { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - - _shape = new TensorShape(memoryLength, lengths, strides); - _reference = ref reference; - } - - /// - /// Returns a reference to specified element of the ReadOnlyTensorSpan. - /// - /// - /// - /// - /// Any index is less than 0 or any index is greater than or equal to FlattenedLength. - /// - public ref readonly T this[params scoped ReadOnlySpan indexes] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if (indexes.Length != Rank) - ThrowHelper.ThrowIndexOutOfRangeException(); - - nint index = TensorSpanHelpers.ComputeLinearIndex(indexes, Strides, Lengths); - if (index >= _shape._memoryLength || index < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return ref Unsafe.Add(ref _reference, index /* force zero-extension */); - } - } - - /// - /// Returns a reference to specified element of the ReadOnlyTensorSpan. - /// - /// - /// - /// - /// Any index is less than 0 or any index is greater than or equal to FlattenedLength. - /// - public ref readonly T this[params scoped ReadOnlySpan indexes] - { - get - { - if (indexes.Length != Rank) - ThrowHelper.ThrowIndexOutOfRangeException(); - - nint index = TensorSpanHelpers.ComputeLinearIndex(indexes, Strides, Lengths); - if (index >= _shape._memoryLength || index < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return ref Unsafe.Add(ref _reference, index /* force zero-extension */); - } - } - - /// - /// Returns a slice of the ReadOnlyTensorSpan. - /// - /// - /// - /// - /// Any index is less than 0 or any index is greater than or equal to FlattenedLength. - /// - public ReadOnlyTensorSpan this[params scoped ReadOnlySpan ranges] - { - get - { - if (ranges.Length != Rank) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return Slice(ranges); - } - } - - /// - /// Gets the number of items in the span. - /// - public nint FlattenedLength => _shape.FlattenedLength; - - /// - /// Gets a value indicating whether this is empty. - /// - /// if this span is empty; otherwise, . - public bool IsEmpty => _shape.IsEmpty; - - /// - /// Gets the length of each dimension in this . - /// - [UnscopedRef] - public ReadOnlySpan Lengths => _shape.Lengths; - - /// - /// Gets the rank, or number of dimensions, of this . - /// - public int Rank => Lengths.Length; - - /// - /// Gets the strides of this . - /// - [UnscopedRef] - public ReadOnlySpan Strides => _shape.Strides; - - /// - /// Compares the given spans and returns false if left and right point at the same memory and have the same length. - /// This opertor does *not* check to see if the *contents* are equal. - /// - public static bool operator !=(ReadOnlyTensorSpan left, ReadOnlyTensorSpan right) => !(left == right); - - /// - /// Compares the given spans and returns true if left and right point at the same memory and have the same length. - /// This operator does *not* check to see if the *contents* are equal. - /// - public static bool operator ==(ReadOnlyTensorSpan left, ReadOnlyTensorSpan right) => - left._shape.FlattenedLength == right._shape.FlattenedLength && - left.Rank == right.Rank && - left._shape.Lengths.SequenceEqual(right._shape.Lengths )&& - Unsafe.AreSame(ref left._reference, ref right._reference); - - /// - /// This method is not supported as spans cannot be boxed. To compare two spans, use operator ==. - /// - /// - /// In all cases. - /// -#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member - [Obsolete("Equals() on ReadOnlyTensorSpan will always throw an exception. Use the equality operator instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => - throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan); - - /// - /// This method is not supported as spans cannot be boxed. - /// - /// - /// In all cases. - /// - [Obsolete("GetHashCode() on ReadOnlyTensorSpan will always throw an exception.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => - throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan); -#pragma warning restore CS0809 - - /// - /// Returns a 0-length read-only span whose base is the null pointer. - /// - public static ReadOnlyTensorSpan Empty => default; - - /// - /// Casts a read-only span of to a read-only span of . - /// - /// The element type of the source read-only span, which must be derived from . - /// The source read-only span. No copy is made. - /// A read-only span with elements cast to the new type. - /// This method uses a covariant cast, producing a read-only span that shares the same memory as the source. The relationships expressed in the type constraints ensure that the cast is a safe operation. - public static ReadOnlyTensorSpan CastUp(ReadOnlyTensorSpan items) where TDerived : class?, T - { - return new ReadOnlyTensorSpan(ref Unsafe.As(ref items._reference), items._shape.Lengths, items._shape.Strides, items._shape._memoryLength); - } - - /// Gets an enumerator for this span. - public Enumerator GetEnumerator() => new Enumerator(this); - - /// Enumerates the elements of a . - public ref struct Enumerator - { - /// The span being enumerated. - private readonly ReadOnlyTensorSpan _span; - /// The current index that the enumerator is on. - private Span _curIndexes; - /// The total item count. - private nint _items; - - /// Initializes the enumerator. - /// The span to enumerate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(ReadOnlyTensorSpan span) - { - _span = span; - _items = -1; - _curIndexes = new nint[_span.Rank]; - _curIndexes[_span.Rank - 1] = -1; - } - - /// Advances the enumerator to the next element of the span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - TensorSpanHelpers.AdjustIndexes(_span.Rank - 1, 1, _curIndexes, _span.Lengths); - - if (_items < _span.FlattenedLength) - _items++; - - return _items < _span.FlattenedLength; - } - - /// Gets the element at the current position of the enumerator. - public ref readonly T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _span[_curIndexes]; - } - } - - /// - /// Returns a reference to the 0th element of the ReadOnlyTensorSpan. If the ReadOnlyTensorSpan is empty, returns null reference. - /// - /// - /// This method can be used for pinning and is required to support the use of span within a fixed statement. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public ref readonly T GetPinnableReference() - { - // Ensure that the native code has just one forward branch that is predicted-not-taken. - ref T ret = ref Unsafe.NullRef(); - if (_shape.FlattenedLength != 0) ret = ref _reference; - return ref ret; - } - - /// - /// Copies the contents of this read-only span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values are in - /// a temporary location before the destination is overwritten. - /// - /// The span to copy items into. - /// - /// The destination ReadOnlyTensorSpan is shorter than the source ReadOnlyTensorSpan. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(scoped TensorSpan destination) - { - // Using "if (!TryCopyTo(...))" results in two branches: one for the length - // check, and one for the result of TryCopyTo. Since these checks are equivalent, - // we can optimize by performing the check once ourselves then calling Memmove directly. - if (TensorHelpers.IsBroadcastableTo(Lengths, destination.Lengths)) - { - scoped Span curIndexes; - nint[]? curIndexesArray; - - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(destination.Rank); - curIndexes = curIndexesArray.AsSpan(0, destination.Rank); - - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[destination.Rank]; - } - curIndexes.Clear(); - - nint copiedValues = 0; - nint[] tempLengths = Tensor.GetSmallestBroadcastableLengths(Lengths, destination.Lengths); - - TensorSpan destinationSlice = destination.Slice(tempLengths); - ReadOnlyTensorSpan srcSlice = Tensor.LazyBroadcast(this, tempLengths); - nint copyLength = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Lengths[^1] : 1; - int indexToAdjust = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Rank - 2 : srcSlice.Rank - 1; - - while (copiedValues < destination.FlattenedLength) - { - TensorSpanHelpers.Memmove(ref Unsafe.Add(ref destinationSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, destinationSlice.Strides, destinationSlice.Lengths)), ref Unsafe.Add(ref srcSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, srcSlice.Strides, srcSlice.Lengths)), copyLength); - TensorSpanHelpers.AdjustIndexes(indexToAdjust, 1, curIndexes, tempLengths); - copiedValues += copyLength; - } - Debug.Assert(copiedValues == destination.FlattenedLength, "Didn't copy the right amount to the array."); - - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); - } - else - { - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - } - } - - /// - /// Copies the contents of this read-only span into destination span. - /// - /// if the copy succeeded; if the destination span is shorter than the source span and no data is written to the destination. - /// The span to copy items into. - /// - /// If the source and destinations overlap, this method behaves as if - /// the original values are in a temporary location before the destination is overwritten. - /// - public bool TryCopyTo(scoped TensorSpan destination) - { - bool retVal = false; - - if (TensorHelpers.IsBroadcastableTo(Lengths, destination.Lengths)) - { - scoped Span curIndexes; - nint[]? curIndexesArray; - - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(destination.Rank); - curIndexes = curIndexesArray.AsSpan(0, destination.Rank); - - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[destination.Rank]; - } - curIndexes.Clear(); - - nint copiedValues = 0; - nint[] tempLengths = Tensor.GetSmallestBroadcastableLengths(Lengths, destination.Lengths); - - TensorSpan destinationSlice = destination.Slice(tempLengths); - ReadOnlyTensorSpan srcSlice = Tensor.LazyBroadcast(this, tempLengths); - nint copyLength = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Lengths[^1] : 1; - int indexToAdjust = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Rank - 2 : srcSlice.Rank - 1; - - while (copiedValues < destination.FlattenedLength) - { - TensorSpanHelpers.Memmove(ref Unsafe.Add(ref destinationSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, destinationSlice.Strides, destinationSlice.Lengths)), ref Unsafe.Add(ref srcSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, srcSlice.Strides, srcSlice.Lengths)), copyLength); - TensorSpanHelpers.AdjustIndexes(indexToAdjust, 1, curIndexes, tempLengths); - copiedValues += copyLength; - } - Debug.Assert(copiedValues == destination.FlattenedLength, "Didn't copy the right amount to the array."); - retVal = true; - - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); - } - - return retVal; - } - - /// - /// Defines an implicit conversion of an array to a . - /// - public static implicit operator ReadOnlyTensorSpan(T[]? array) => new ReadOnlyTensorSpan(array); - - /// - /// Returns a with the name of the type and the number of elements. - /// - public override string ToString() => $"System.Numerics.Tensors.ReadOnlyTensorSpan<{typeof(T).Name}>[{_shape.FlattenedLength}]"; - - /// - /// Returns a reference to specified element of the TensorSpan. - /// - /// The indexes for the slice. - /// - /// Any index is less than 0 or greater than or equal to FlattenedLength. - /// - public ReadOnlyTensorSpan Slice(params scoped ReadOnlySpan indexes) - { - NRange[] ranges = new NRange[indexes.Length]; - for (int i = 0; i < indexes.Length; i++) - { - ranges[i] = new NRange(checked((int)indexes[i].GetOffset(Lengths[i])), Lengths[i]); - } - return Slice(ranges); - } - - /// - /// Takes in the lengths of the dimensions and slices according to them. - /// - /// The dimension lengths. - /// A based on the provided . - internal ReadOnlyTensorSpan Slice(scoped ReadOnlySpan lengths) - { - NRange[] ranges = new NRange[lengths.Length]; - for (int i = 0; i < lengths.Length; i++) - { - ranges[i] = new NRange(0, lengths[i]); - } - return Slice(ranges); - } - - /// - /// Forms a slice out of the given span. - /// - /// The ranges for the slice. - /// A based on the provided . - - public ReadOnlyTensorSpan Slice(params scoped ReadOnlySpan ranges) - { - if (ranges.Length != Lengths.Length) - throw new ArgumentOutOfRangeException(nameof(ranges), "Number of dimensions to slice does not equal the number of dimensions in the span"); - - ReadOnlyTensorSpan toReturn; - scoped Span lengths; - scoped Span offsets; - nint[]? lengthsArray; - nint[]? offsetsArray; - if (Rank > TensorShape.MaxInlineRank) - { - lengthsArray = ArrayPool.Shared.Rent(Rank); - lengths = lengthsArray.AsSpan(0, Rank); - - offsetsArray = ArrayPool.Shared.Rent(Rank); - offsets = offsetsArray.AsSpan(0, Rank); - } - else - { - lengths = stackalloc nint[Rank]; - offsets = stackalloc nint[Rank]; - - lengthsArray = null; - offsetsArray = null; - } - lengths.Clear(); - offsets.Clear(); - - for (int i = 0; i < ranges.Length; i++) - { - (offsets[i], lengths[i]) = ranges[i].GetOffsetAndLength(Lengths[i]); - } - - // FlattenedLength is computed everytime so using a local to cache the value. - nint flattenedLength = FlattenedLength; - nint index = 0; - - if (flattenedLength != 0) - { - for (int i = 0; i < offsets.Length; i++) - { - index += Strides[i] * (offsets[i]); - } - } - - if ((index >= _shape._memoryLength || index < 0) && flattenedLength != 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - - toReturn = new ReadOnlyTensorSpan(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index); - - if (offsetsArray != null) - ArrayPool.Shared.Return(offsetsArray); - if (lengthsArray != null) - ArrayPool.Shared.Return(lengthsArray); - - return toReturn; - } - - /// - /// Flattens the contents of this span into the provided . - /// - /// The span to copy items into. - public bool TryFlattenTo(scoped Span destination) - { - bool retVal = false; - if (destination.Length < _shape.FlattenedLength) - { - scoped Span curIndexes; - nint[]? curIndexesArray; - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray.AsSpan(0, Rank); - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[Rank]; - } - curIndexes.Clear(); - - nint copiedValues = 0; - while (copiedValues < _shape.FlattenedLength) - { - TensorSpanHelpers.Memmove(destination.Slice(checked((int)copiedValues)), ref Unsafe.Add(ref _reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, Strides, Lengths)), Lengths[Rank - 1]); - TensorSpanHelpers.AdjustIndexes(Rank - 2, 1, curIndexes, _shape.Lengths); - copiedValues += Lengths[Rank - 1]; - } - - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); - retVal = true; - } - return retVal; - } - - /// - /// Flattens the contents of this span into the provided . - /// - /// The span to copy items into. - public void FlattenTo(scoped Span destination) - { - if (destination.Length < _shape.FlattenedLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - if (_shape.FlattenedLength == 0) - return; - - scoped Span curIndexes; - nint[]? curIndexesArray; - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray.AsSpan(0, Rank); - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[Rank]; - } - curIndexes.Clear(); - - nint copiedValues = 0; - while (copiedValues < _shape.FlattenedLength) - { - if (Strides[Rank - 1] == 0) - { - destination.Slice(checked((int)copiedValues), (int)Lengths[Rank - 1]).Fill(Unsafe.Add(ref _reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, Strides, Lengths))); - } - else - { - TensorSpanHelpers.Memmove(destination.Slice(checked((int)copiedValues)), ref Unsafe.Add(ref _reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, Strides, Lengths)), Lengths[Rank - 1]); - } - TensorSpanHelpers.AdjustIndexes(Rank - 2, 1, curIndexes, _shape.Lengths); - copiedValues += Lengths[Rank - 1]; - } - - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); - } - } -} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs new file mode 100644 index 00000000000000..36452121c96ebd --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan_1.cs @@ -0,0 +1,524 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + +namespace System.Numerics.Tensors +{ + /// + /// Represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed + /// or native memory, or to memory allocated on the stack. It is type-safe and memory-safe. + /// + [DebuggerTypeProxy(typeof(TensorSpanDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] + [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public readonly ref struct ReadOnlyTensorSpan + { + /// + public static ReadOnlyTensorSpan Empty => default; + + internal readonly TensorShape _shape; + internal readonly ref T _reference; + + /// Creates a new tensor over the entirety of the target array. + /// The target array. + /// + /// Returns default when is null. + /// The created tensor span has a single dimension that is the same length as . + /// + public ReadOnlyTensorSpan(T[]? array) + { + _shape = TensorShape.Create(array); + _reference = ref (array is not null) + ? ref MemoryMarshal.GetArrayDataReference(array) + : ref Unsafe.NullRef(); + } + + /// Creates a new tensor over the portion of the target array using the specified lengths. + /// The target array. + /// The lengths of the dimensions. If an empty span is provided, the created tensor will have a single dimension that is the same length as . + /// Returns default when is null. + /// + /// Thrown when one of the following conditions is met: + /// * is null and is not empty + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than .Length + /// + public ReadOnlyTensorSpan(T[]? array, scoped ReadOnlySpan lengths) + { + _shape = TensorShape.Create(array, lengths); + _reference = ref (array is not null) + ? ref MemoryMarshal.GetArrayDataReference(array) + : ref Unsafe.NullRef(); + } + + /// Creates a new tensor over the portion of the target array beginning at the specified start index and using the specified lengths and strides. + /// The target array. + /// The lengths of the dimensions. If an empty span is provided, the created tensor will have a single dimension that is the same length as . + /// The strides of each dimension. If an empty span is provided, then strides will be automatically calculated from . + /// Returns default when is null. + /// + /// Thrown when one of the following conditions is met: + /// * is null and or is not empty + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than .Length + /// * is not empty and has a length different from + /// * is not empty and contains an element that is negative + /// * is not empty and contains an element that is zero in a non leading position + /// + public ReadOnlyTensorSpan(T[]? array, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(array, lengths, strides); + _reference = ref (array is not null) + ? ref MemoryMarshal.GetArrayDataReference(array) + : ref Unsafe.NullRef(); + } + + /// Creates a new tensor over the portion of the target array beginning at the specified start index and using the specified lengths and strides. + /// The target array. + /// The index at which to begin the tensor. + /// The lengths of the dimensions. If an empty span is provided, the created tensor will have a single dimension that is the same length as . + /// The strides of each dimension. If an empty span is provided, then strides will be automatically calculated from . + /// Returns default when is null. + /// + /// Thrown when one of the following conditions is met: + /// * is null and or is not empty + /// * is not in range of + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than .Length + /// * is not empty and has a length different from + /// * is not empty and contains an element that is negative + /// * is not empty and contains an element that is zero in a non leading position + /// + public ReadOnlyTensorSpan(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(array, start, lengths, strides); + _reference = ref (array is not null) + ? ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (uint)start) + : ref Unsafe.NullRef(); + } + + /// Creates a new tensor span over the entirety of the target span. + /// The target span. + /// The created tensor span has a single dimension that is the same length as . + public ReadOnlyTensorSpan(ReadOnlySpan span) + { + ref T reference = ref MemoryMarshal.GetReference(span); + _shape = TensorShape.Create(ref reference, span.Length); + _reference = ref reference; + } + + /// Creates a new tensor span over the target span using the specified lengths. + /// The target span. + /// The lengths of the dimensions. If an empty span is provided, the created tensor span will have a single dimension that is the same length as . + /// + /// Thrown when one of the following conditions is met: + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than .Length + /// + public ReadOnlyTensorSpan(ReadOnlySpan span, scoped ReadOnlySpan lengths) + { + ref T reference = ref MemoryMarshal.GetReference(span); + _shape = TensorShape.Create(ref reference, span.Length, lengths); + _reference = ref reference; + } + + /// Creates a new tensor span over the target span using the specified lengths and strides. + /// The target span. + /// The lengths of the dimensions. If an empty span is provided, the created tensor span will have a single dimension that is the same length as . + /// The strides of each dimension. If an empty span is provided, then strides will be automatically calculated from . + /// + /// Thrown when one of the following conditions is met: + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than .Length + /// * is not empty and has a length different from + /// * is not empty and contains an element that is negative + /// * is not empty and contains an element that is zero in a non leading position + /// + public ReadOnlyTensorSpan(ReadOnlySpan span, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + ref T reference = ref MemoryMarshal.GetReference(span); + _shape = TensorShape.Create(ref reference, span.Length, lengths, strides); + _reference = ref reference; + } + + /// Creates a new tensor span over the entirety of the target array. + /// The target array. + /// + /// Returns default when is null. + /// The created tensor span has a single dimension that is the same length as . + /// + public ReadOnlyTensorSpan(Array? array) + { + _shape = TensorShape.Create(array); + _reference = ref (array is not null) + ? ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)) + : ref Unsafe.NullRef(); + } + + /// Creates a new tensor span over the portion of the target array beginning at the specified start index and using the specified lengths and strides. + /// The target array. + /// The index at which to begin the tensor span. + /// The lengths of the dimensions. If an empty span is provided, the created tensor span will have a single dimension that is the same length as . + /// The strides of each dimension. If an empty span is provided, then strides will be automatically calculated from . + /// + /// Returns default when is null. + /// + /// + /// + /// Thrown when one of the following conditions is met: + /// * is null and or is not empty + /// * is not in range of + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than .Length + /// * is not empty and has a length different from + /// * is not empty and contains an element that is negative + /// * is not empty and contains an element that is zero in a non leading position + /// + public ReadOnlyTensorSpan(Array? array, scoped ReadOnlySpan start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(array, start, lengths, strides, out nint linearOffset); + _reference = ref (array is not null) + ? ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), linearOffset) + : ref Unsafe.NullRef(); + } + + /// Creates a new tensor span over the target unmanaged buffer. + /// The pointer to the start of the target unmanaged buffer. + /// The number of elements the target unmanaged buffer contains. + /// Returns default when is null. + /// is null and is not zero + + [CLSCompliant(false)] + public unsafe ReadOnlyTensorSpan(T* data, nint dataLength) + { + _shape = TensorShape.Create(data, dataLength); + _reference = ref Unsafe.AsRef(data); + } + + /// Creates a new tensor span over the target unmanaged buffer using the specified lengths. + /// The pointer to the start of the target unmanaged buffer. + /// The number of elements the target unmanaged buffer contains. + /// The lengths of the dimensions. If an empty span is provided, the created tensor span will have a single dimension that is the same length as . + /// Returns default when is null. + /// + /// Thrown when one of the following conditions is met: + /// * is null and is not zero + /// * is null and + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than + /// + [CLSCompliant(false)] + public unsafe ReadOnlyTensorSpan(T* data, nint dataLength, scoped ReadOnlySpan lengths) + { + _shape = TensorShape.Create(data, dataLength, lengths); + _reference = ref Unsafe.AsRef(data); + } + + /// Creates a new tensor span over the target unmanaged buffer using the specified lengths and strides. + /// The pointer to the start of the target unmanaged buffer. + /// The number of elements the target unmanaged buffer contains. + /// The lengths of the dimensions. If an empty span is provided, the created tensor span will have a single dimension that is the same length as . + /// The strides of each dimension. If an empty span is provided, then strides will be automatically calculated from . + /// Returns default when is null. + /// + /// Thrown when one of the following conditions is met: + /// * is null and is not zero + /// * is null and or is not empty + /// * is not empty and contains an element that is either zero or negative + /// * is not empty and has a flattened length greater than + /// * is not empty and has a length different from + /// * is not empty and contains an element that is negative + /// * is not empty and contains an element that is zero in a non leading position + /// + [CLSCompliant(false)] + public unsafe ReadOnlyTensorSpan(T* data, nint dataLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(data, dataLength, lengths, strides); + _reference = ref Unsafe.AsRef(data); + } + + // Constructor for internal use only. It is not safe to expose publicly. + internal ReadOnlyTensorSpan(ref T data, nint dataLength) + { + _shape = TensorShape.Create(ref data, dataLength); + _reference = ref data; + } + + internal ReadOnlyTensorSpan(ref T data, nint dataLength, scoped ReadOnlySpan lengths) + { + _shape = TensorShape.Create(ref data, dataLength, lengths); + _reference = ref data; + } + + internal ReadOnlyTensorSpan(ref T data, nint dataLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(ref data, dataLength, lengths, strides); + _reference = ref data; + } + + internal ReadOnlyTensorSpan(ref T data, nint dataLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, scoped ReadOnlySpan linearRankOrder) + { + _shape = TensorShape.Create(ref data, dataLength, lengths, strides, linearRankOrder); + _reference = ref data; + } + + internal ReadOnlyTensorSpan(ref T reference, scoped in TensorShape shape) + { + _reference = ref reference; + _shape = shape; + } + + /// + public ref readonly T this[params scoped ReadOnlySpan indexes] + { + get => ref Unsafe.Add(ref _reference, _shape.GetLinearOffset(indexes)); + } + + /// + public ref readonly T this[params scoped ReadOnlySpan indexes] + { + get => ref Unsafe.Add(ref _reference, _shape.GetLinearOffset(indexes)); + } + + /// + public ReadOnlyTensorSpan this[params scoped ReadOnlySpan ranges] + { + get => Slice(ranges); + } + + /// + public nint FlattenedLength => _shape.FlattenedLength; + + internal bool IsContiguousAndDense => _shape.IsContiguousAndDense; + + /// + public bool IsEmpty => _shape.IsEmpty; + + /// + [UnscopedRef] + public ReadOnlySpan Lengths => _shape.Lengths; + + /// + public int Rank => Lengths.Length; + + /// + [UnscopedRef] + public ReadOnlySpan Strides => _shape.Strides; + + /// Returns a value that indicates whether two tensor spans are equal. + /// The first tensor span to compare. + /// The second tensor span to compare. + /// true if the two tensor span are equal; otherwise, false. + /// Two tensor span are equal if they have the same length and the corresponding elements of and point to the same memory. Note that the test for equality does not attempt to determine whether the contents are equal. + public static bool operator ==(in ReadOnlyTensorSpan left, in ReadOnlyTensorSpan right) + => Unsafe.AreSame(ref left._reference, ref right._reference) + && left._shape == right._shape; + + /// Returns a value that indicates whether two tensor spans are not equal. + /// The first tensor span to compare. + /// The second tensor span to compare. + /// true if the two tensor span are not equal; otherwise, false. + /// Two tensor span are not equal if they have the different lengths or if the corresponding elements of and do not point to the same memory. Note that the test for equality does not attempt to determine whether the contents are not equal. + public static bool operator !=(in ReadOnlyTensorSpan left, in ReadOnlyTensorSpan right) => !(left == right); + + /// Defines an implicit conversion of an array to a readonly tensor span. + /// The array to convert to a readonly tensor span. + /// The readonly tensor span that corresponds to . + public static implicit operator ReadOnlyTensorSpan(T[]? array) => new ReadOnlyTensorSpan(array); + + /// Casts a tensor span of to a tensor span of . + /// The element type of the source tensor span, which must be derived from . + /// The source tensor span. No copy is made. + /// A tensor span with elements cast to the new type. + /// This method uses a covariant cast, producing a tensor span that shares the same memory as the source. The relationships expressed in the type constraints ensure that the cast is a safe operation. + public static ReadOnlyTensorSpan CastUp(in ReadOnlyTensorSpan items) + where TDerived : class?, T + { + return new ReadOnlyTensorSpan( + ref Unsafe.As(ref items._reference), + items._shape + ); + } + + /// + public void CopyTo(scoped in TensorSpan destination) + { + if (!TryCopyTo(destination)) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + } + + /// Calls to this method are not supported. + /// Not supported. + /// Calls to this method are not supported. + /// Calls to this method are not supported. + /// This method is not supported as tensor spans cannot be boxed. To compare two tensor spans, use operator ==. + [Obsolete("Equals() on ReadOnlyTensorSpan will always throw an exception. Use the equality operator instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => + throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan); + + /// + public void FlattenTo(scoped Span destination) + { + if (!TryFlattenTo(destination)) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + } + + /// Gets an enumerator for the readonly tensor span. + public Enumerator GetEnumerator() => new Enumerator(this); + + /// Calls to this method are not supported. + /// Calls to this method are not supported. + /// Calls to this method are not supported. + /// This method is not supported as tensor spans cannot be boxed. + [Obsolete("GetHashCode() on ReadOnlyTensorSpan will always throw an exception.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => + throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public ref readonly T GetPinnableReference() + { + // Ensure that the native code has just one forward branch that is predicted-not-taken. + ref T ret = ref Unsafe.NullRef(); + if (_shape.FlattenedLength != 0) ret = ref _reference; + return ref ret; + } + + /// + public ReadOnlyTensorSpan Slice(params scoped ReadOnlySpan startIndexes) + { + TensorShape shape = _shape.Slice(startIndexes, out nint linearOffset); + return new ReadOnlyTensorSpan( + ref Unsafe.Add(ref _reference, linearOffset), + shape + ); + } + + /// + public ReadOnlyTensorSpan Slice(params scoped ReadOnlySpan startIndexes) + { + TensorShape shape = _shape.Slice(startIndexes, out nint linearOffset); + return new ReadOnlyTensorSpan( + ref Unsafe.Add(ref _reference, linearOffset), + shape + ); + } + + /// + public ReadOnlyTensorSpan Slice(params scoped ReadOnlySpan ranges) + { + TensorShape shape = _shape.Slice(ranges, out nint linearOffset); + return new ReadOnlyTensorSpan( + ref Unsafe.Add(ref _reference, linearOffset), + shape + ); + } + + /// Returns the string representation of the tensor span. + /// The string representation of the tensor span. + public override string ToString() => $"System.Numerics.Tensors.ReadOnlyTensorSpan<{typeof(T).Name}>[{_shape}]"; + + /// + public bool TryCopyTo(scoped in TensorSpan destination) + { + if (TensorShape.AreCompatible(destination._shape, _shape, false)) + { + TensorOperation.Invoke, T, T>(this, destination); + return true; + } + return false; + } + + /// + public bool TryFlattenTo(scoped Span destination) + { + if (_shape.FlattenedLength <= destination.Length) + { + TensorOperation.Invoke, T, T>(this, destination); + return true; + } + return false; + } + + /// Enumerates the elements of a tensor span. + public ref struct Enumerator : IEnumerator + { + private readonly ReadOnlyTensorSpan _span; + private nint[] _indexes; + private nint _linearOffset; + private nint _itemsEnumerated; + + internal Enumerator(ReadOnlyTensorSpan span) + { + _span = span; + _indexes = new nint[span.Rank]; + + _indexes[^1] = -1; + + _linearOffset = 0 - (!span.IsEmpty ? span.Strides[^1] : 0); + _itemsEnumerated = 0; + } + + /// Gets the element at the current position of the enumerator. + public readonly ref readonly T Current => ref Unsafe.Add(ref _span._reference, _linearOffset); + + /// Advances the enumerator to the next element of the tensor span. + public bool MoveNext() + { + if (_itemsEnumerated == _span._shape.FlattenedLength) + { + return false; + } + + _linearOffset = _span._shape.AdjustToNextIndex(_span._shape, _linearOffset, _indexes); + + _itemsEnumerated++; + return true; + } + + /// Sets the enumerator to its initial position, which is before the first element in the tensor span. + public void Reset() + { + Array.Clear(_indexes); + _indexes[^1] = -1; + + _linearOffset = 0 - (!_span.IsEmpty ? _span.Strides[^1] : 0); + _itemsEnumerated = 0; + } + + // + // IDisposable + // + + void IDisposable.Dispose() { } + + // + // IEnumerator + // + + readonly object? IEnumerator.Current => Current; + + // + // IEnumerator + // + + readonly T IEnumerator.Current => Current; + } + } +} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs deleted file mode 100644 index 29b48f4a613e03..00000000000000 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.InteropServices; -using Microsoft.VisualBasic; - -#pragma warning disable CS8601 // Possible null reference assignment. -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - -namespace System.Numerics.Tensors -{ - /// - /// Provides methods for creating tensors. - /// - public static partial class Tensor - { - /// - /// Creates a and initializes it with the default value of T. If is true, the memory will be pinned. - /// - /// A indicating the lengths of each dimension. - /// A whether the underlying data should be pinned or not. - public static Tensor Create(scoped ReadOnlySpan lengths, bool pinned = false) - { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = pinned ? GC.AllocateArray((int)linearLength, pinned) : (new T[linearLength]); - return Create(values, lengths, [], pinned); - } - - /// - /// Creates a and initializes it with the default value of T. If is true, the memory will be pinned. - /// - /// A indicating the lengths of each dimension. - /// A indicating the strides of each dimension. - /// A whether the underlying data should be pinned or not. - public static Tensor Create(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false) - { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = pinned ? GC.AllocateArray((int)linearLength, pinned) : (new T[linearLength]); - return Create(values, lengths, strides, pinned); - } - - /// - /// Creates a from the provided . If the product of the - /// does not equal the length of the array, an exception will be thrown. - /// - /// An array of the backing memory. - /// A indicating the lengths of each dimension. - /// A indicating whether the were pinned or not. - /// - public static Tensor Create(T[] values, scoped ReadOnlySpan lengths, bool pinned = false) - => Create(values, lengths, [], pinned); - - /// - /// Creates a from the provided . If the product of the - /// does not equal the length of the array, an exception will be thrown. - /// - /// An array of the backing memory. - /// A indicating the lengths of each dimension. - /// A indicating the strides of each dimension. - /// A indicating whether the were pinned or not. - /// - public static Tensor Create(T[] values, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false) - { - return new Tensor(values, lengths, strides, memoryOffset: 0, pinned); - } - - /// - /// Creates a and initializes it with the data from . - /// - /// A with the data to use for the initialization. - /// A indicating the lengths of each dimension. - /// A indicating whether the were pinned or not. - - public static Tensor Create(IEnumerable values, scoped ReadOnlySpan lengths, bool pinned = false) - { - T[] data = values.ToArray(); - return new Tensor(data, lengths.IsEmpty ? [data.Length] : lengths, memoryOffset: 0, pinned); - } - - /// - /// Creates a and initializes it with the data from . - /// - /// A with the data to use for the initialization. - /// A indicating the lengths of each dimension. - /// A indicating the strides of each dimension. - /// A indicating whether the were pinned or not. - public static Tensor Create(IEnumerable values, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false) - { - T[] data = values.ToArray(); - return new Tensor(data, lengths.IsEmpty ? [data.Length] : lengths, strides, memoryOffset: 0, pinned); - } - - #region Normal - /// - /// Creates a and initializes it with random data in a gaussian normal distribution. - /// - /// A indicating the lengths of each dimension. - public static Tensor CreateAndFillGaussianNormalDistribution(params scoped ReadOnlySpan lengths) - where T : IFloatingPoint - { - return CreateAndFillGaussianNormalDistribution(Random.Shared, lengths); - } - - /// - /// Creates a and initializes it with random data in a gaussian normal distribution. - /// - /// - /// A indicating the lengths of each dimension. - public static Tensor CreateAndFillGaussianNormalDistribution(Random random, params scoped ReadOnlySpan lengths) - where T : IFloatingPoint - { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = new T[linearLength]; - GaussianDistribution(values, linearLength, random); - return new Tensor(values, lengths, memoryOffset: 0, isPinned: false); - } - - private static void GaussianDistribution(in Span values, nint linearLength, Random random) - where T : IFloatingPoint - { - for (int i = 0; i < linearLength; i++) - { - double u1 = 1.0 - random.NextDouble(); - double u2 = 1.0 - random.NextDouble(); - values[i] = T.CreateChecked(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); - } - } - #endregion - - - /// - /// Creates a and initializes it with random data uniformly distributed. - /// - /// A indicating the lengths of each dimension. - public static Tensor CreateAndFillUniformDistribution(params scoped ReadOnlySpan lengths) - where T : IFloatingPoint - { - return CreateAndFillUniformDistribution(Random.Shared, lengths); - } - - /// - /// Creates a and initializes it with random data uniformly distributed. - /// - /// - /// A indicating the lengths of each dimension. - public static Tensor CreateAndFillUniformDistribution(Random random, params scoped ReadOnlySpan lengths) - where T : IFloatingPoint - { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = new T[linearLength]; - for (int i = 0; i < values.Length; i++) - values[i] = T.CreateChecked(random.NextDouble()); - - return new Tensor(values, lengths, memoryOffset: 0, isPinned: false); - } - - /// - /// Creates a and does not initialize it. If is true, the memory will be pinned. - /// - /// A indicating the lengths of each dimension. - /// A whether the underlying data should be pinned or not. - public static Tensor CreateUninitialized(scoped ReadOnlySpan lengths, bool pinned = false) - => CreateUninitialized(lengths, [], pinned); - - /// - /// Creates a and does not initialize it. If is true, the memory will be pinned. - /// - /// A indicating the lengths of each dimension. - /// A indicating the strides of each dimension. - /// A whether the underlying data should be pinned or not. - public static Tensor CreateUninitialized(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false) - { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = GC.AllocateUninitializedArray((int)linearLength, pinned); - return new Tensor(values, lengths, strides, memoryOffset: 0, pinned); - } - - /// - /// Fills the given with random data in a Gaussian normal distribution. - /// can optionally be provided for seeding. - /// - /// The element type. - /// The destination where the data will be stored. - /// to provide random seeding. Defaults to if not provided. - /// - public static ref readonly TensorSpan FillGaussianNormalDistribution(in TensorSpan destination, Random? random = null) where T : IFloatingPoint - { - Span span = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - - GaussianDistribution(span, destination._shape._memoryLength, random ?? Random.Shared); - - return ref destination; - } - - /// - /// Fills the given with random data in a uniform distribution. - /// can optionally be provided for seeding. - /// - /// The element type. - /// The destination where the data will be stored. - /// to provide random seeding. Defaults to if not provided. - /// - public static ref readonly TensorSpan FillUniformDistribution(in TensorSpan destination, Random? random = null) where T : IFloatingPoint - { - Span span = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - random ??= Random.Shared; - for (int i = 0; i < span.Length; i++) - span[i] = T.CreateChecked(random.NextDouble()); - - return ref destination; - } - } -} 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 4878350449c319..aaf9612ae0a0b3 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 @@ -1,751 +1,5624 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Buffers; -using System.Collections; using System.Collections.Generic; -using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Reflection.Metadata.Ecma335; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using static System.Runtime.InteropServices.JavaScript.JSType; - -#pragma warning disable CS8601 // Possible null reference assignment. -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. namespace System.Numerics.Tensors { - /// - /// Represents a tensor. - /// + /// Provides methods for tensor operations. [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - public sealed class Tensor : ITensor, ITensor, T> + public static partial class Tensor { - /// A byref or a native ptr. - internal readonly T[] _values; - /// The number of elements this Tensor contains. - internal readonly nint _flattenedLength; - /// The lengths of each dimension. - internal readonly nint[] _lengths; - /// The strides representing the memory offsets for each dimension. - internal readonly nint[] _strides; - /// If the backing memory is permanently pinned (so not just using a fixed statement). - internal readonly bool _isPinned; - /// The offset of the first element in the backing memory. - internal readonly int _memoryOffset; - - /// - /// Creates a new empty Tensor. - /// - internal Tensor() - { - _flattenedLength = 0; - _values = []; - _lengths = []; - _strides = []; - _memoryOffset = 0; - } + #region AsTensorSpan + /// + public static ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array) + => new ReadOnlyTensorSpan(array); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Tensor(T[]? values, ReadOnlySpan lengths, int memoryOffset, bool isPinned = false) : this(values, lengths, Array.Empty(), memoryOffset, isPinned) { } + /// + public static ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, scoped ReadOnlySpan lengths) + => new ReadOnlyTensorSpan(array, lengths); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Tensor(T[]? values, ReadOnlySpan lengths, ReadOnlySpan strides, int memoryOffset, bool isPinned = false) - { - if (values == null) - { - if (_flattenedLength != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - _flattenedLength = 0; - _values = []; - _lengths = []; - _strides = []; - _memoryOffset = memoryOffset; - return; // returns default - } + /// + public static ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => new ReadOnlyTensorSpan(array, lengths, strides); - _lengths = lengths.IsEmpty ? [values.Length] : lengths.ToArray(); - _memoryOffset = memoryOffset; + /// + public static ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => new ReadOnlyTensorSpan(array, start, lengths, strides); - if (_memoryOffset < 0 || (_memoryOffset >= values.Length && values.Length != 0 )) - ThrowHelper.ThrowIndexOutOfRangeException(); + /// + public static TensorSpan AsTensorSpan(this T[]? array) + => new TensorSpan(array); - _flattenedLength = TensorSpanHelpers.CalculateTotalLength(_lengths); - _strides = strides.IsEmpty ? TensorSpanHelpers.CalculateStrides(_lengths, _flattenedLength) : strides.ToArray(); - TensorSpanHelpers.ValidateStrides(_strides, _lengths); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(_strides, _lengths); + /// + public static TensorSpan AsTensorSpan(this T[]? array, scoped ReadOnlySpan lengths) + => new TensorSpan(array, lengths); - if (Environment.Is64BitProcess) - { - // See comment in Span.Slice for how this works. - if ((ulong)(uint)maxElements >= (ulong)(uint)(values.Length - memoryOffset) && values.Length != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - } - else - { - if (((uint)maxElements >= (uint)(values.Length - memoryOffset)) && values.Length != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - } + /// + public static TensorSpan AsTensorSpan(this T[]? array, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => new TensorSpan(array, lengths, strides); - _values = values; - _isPinned = isPinned; - } + /// + public static TensorSpan AsTensorSpan(this T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) => new TensorSpan(array, start, lengths, strides); + #endregion + #region Broadcast /// - /// Creates a and initializes it with the default value of T. If is true, the memory will be pinned. + /// Broadcast the data from to the smallest broadcastable shape compatible with . Creates a new and allocates new memory. /// - /// A indicating the lengths of each dimension. - /// A whether the underlying data should be pinned or not. - static Tensor ITensor, T>.Create(scoped ReadOnlySpan lengths, bool pinned) + /// Input . + /// Other to make shapes broadcastable. + public static Tensor Broadcast(scoped in ReadOnlyTensorSpan source, scoped in ReadOnlyTensorSpan lengthsSource) { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = pinned ? GC.AllocateArray((int)linearLength, pinned) : (new T[linearLength]); - return new Tensor(values, lengths.ToArray(), memoryOffset: 0, pinned); + return Broadcast(source, lengthsSource.Lengths); } /// - /// Creates a and initializes it with the default value of T. If is true, the memory will be pinned. + /// Broadcast the data from to the new shape . Creates a new and allocates new memory. + /// If the shape of the is not compatible with the new shape, an exception is thrown. /// - /// A indicating the lengths of each dimension. - /// A indicating the strides of each dimension. - /// A whether the underlying data should be pinned or not. - static Tensor ITensor, T>.Create(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned) + /// Input . + /// of the desired new shape. + /// Thrown when the shapes are not broadcast compatible. + public static Tensor Broadcast(scoped in ReadOnlyTensorSpan source, scoped ReadOnlySpan lengths) { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = pinned ? GC.AllocateArray((int)linearLength, pinned) : (new T[linearLength]); - return new Tensor(values, lengths.ToArray(), strides.ToArray(), memoryOffset: 0, pinned); + TensorOperation.ValidateCompatibility(source, lengths); + Tensor destination = Tensor.CreateUninitialized(lengths); + TensorOperation.Invoke, T, T>(source, destination); + return destination; } + #endregion + #region BroadcastTo /// - /// Creates a and does not initialize it. If is true, the memory will be pinned. + /// Broadcast the data from to . /// - /// A indicating the lengths of each dimension. - /// A whether the underlying data should be pinned or not. - static Tensor ITensor, T>.CreateUninitialized(scoped ReadOnlySpan lengths, bool pinned) + /// Input . + /// + public static void BroadcastTo(this Tensor source, in TensorSpan destination) { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = GC.AllocateUninitializedArray((int)linearLength, pinned); - return new Tensor(values, lengths.ToArray(), memoryOffset: 0, pinned); + TensorOperation.ValidateCompatibility(source, destination); + TensorOperation.Invoke, T, T>(source, destination); } /// - /// Creates a and does not initialize it. If is true, the memory will be pinned. + /// Broadcast the data from to . /// - /// A indicating the lengths of each dimension. - /// A indicating the strides of each dimension. - /// A whether the underlying data should be pinned or not. - static Tensor ITensor, T>.CreateUninitialized(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned) + /// Input . + /// Other to make shapes broadcastable. + public static void BroadcastTo(in this TensorSpan source, in TensorSpan destination) { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = GC.AllocateUninitializedArray((int)linearLength, pinned); - return new Tensor(values, lengths.ToArray(), strides.ToArray(), memoryOffset: 0, pinned); + TensorOperation.ValidateCompatibility(source, destination); + TensorOperation.Invoke, T, T>(source, destination); } - // ITensor /// - /// The Empty Tensor. + /// Broadcast the data from to . /// - public static Tensor Empty { get; } = new(); + /// Input . + /// + public static void BroadcastTo(in this ReadOnlyTensorSpan source, in TensorSpan destination) + { + TensorOperation.ValidateCompatibility(source, destination); + TensorOperation.Invoke, T, T>(source, destination); + } + #endregion + #region Concatenate /// - /// Gets a value indicating whether this is empty. + /// Join a sequence of tensors along an existing axis. /// - /// if this tensor is empty; otherwise, . - public bool IsEmpty => _lengths.Length == 0; + /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). + public static Tensor Concatenate(params scoped ReadOnlySpan> tensors) + { + return ConcatenateOnDimension(0, tensors); + } /// - /// Gets a value indicating whether the backing memory of the is pinned."/> + /// Join a sequence of tensors along an existing axis. /// - /// if the backing memory is pinned; otherwise, . - public bool IsPinned => _isPinned; + /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). + /// The axis along which the tensors will be joined. If axis is -1, arrays are flattened before use. Default is 0. + public static Tensor ConcatenateOnDimension(int dimension, params scoped ReadOnlySpan> tensors) + { + if (tensors.Length < 2) + ThrowHelper.ThrowArgument_ConcatenateTooFewTensors(); - /// - /// Gets a value indicating the rank, or number of dimensions, of this . - /// - /// with the number of dimensions. - public int Rank => _lengths.Length; + if (dimension < -1 || dimension > tensors[0].Rank) + ThrowHelper.ThrowArgument_InvalidAxis(); - /// - /// The number of items in the . - /// - /// with the number of items. - public nint FlattenedLength => _flattenedLength; + // Calculate total space needed. + nint totalLength = 0; + for (int i = 0; i < tensors.Length; i++) + totalLength += tensors[i].FlattenedLength; - /// - /// Gets the length of each dimension in this . - /// - /// with the lengths of each dimension. - public ReadOnlySpan Lengths => _lengths; + nint sumOfAxis = 0; + // If axis != -1, make sure all dimensions except the one to concatenate on match. + if (dimension != -1) + { + sumOfAxis = tensors[0].Lengths[dimension]; + for (int i = 1; i < tensors.Length; i++) + { + if (tensors[0].Rank != tensors[i].Rank) + ThrowHelper.ThrowArgument_InvalidConcatenateShape(); + for (int j = 0; j < tensors[0].Rank; j++) + { + if (j != dimension) + { + if (tensors[0].Lengths[j] != tensors[i].Lengths[j]) + ThrowHelper.ThrowArgument_InvalidConcatenateShape(); + } + } + sumOfAxis += tensors[i].Lengths[dimension]; + } + } - /// - /// Gets the length of each dimension in this . - /// - /// with the lengths of each dimension. - ReadOnlySpan IReadOnlyTensor.Lengths => _lengths; + Tensor tensor; + if (dimension == -1) + { + tensor = Tensor.Create([totalLength]); + } + else + { + nint[] lengths = new nint[tensors[0].Rank]; + tensors[0].Lengths.CopyTo(lengths); + lengths[dimension] = sumOfAxis; + tensor = Tensor.Create(lengths); + } + ConcatenateOnDimension(dimension, tensors, tensor); + return tensor; + } /// - /// Gets the strides of each dimension in this . + /// Join a sequence of tensors along an existing axis. /// - /// with the strides of each dimension. - public ReadOnlySpan Strides => _strides; + /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). + /// + public static ref readonly TensorSpan Concatenate(scoped ReadOnlySpan> tensors, in TensorSpan destination) + { + return ref ConcatenateOnDimension(0, tensors, destination); + } /// - /// Gets the strides of each dimension in this . + /// Join a sequence of tensors along an existing axis. /// - /// with the strides of each dimension. - ReadOnlySpan IReadOnlyTensor.Strides => _strides; + /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). + /// The axis along which the tensors will be joined. If axis is -1, arrays are flattened before use. Default is 0. + /// - bool ITensor.IsReadOnly => false; + public static ref readonly TensorSpan ConcatenateOnDimension(int dimension, scoped ReadOnlySpan> tensors, in TensorSpan destination) + { + if (tensors.Length < 2) + ThrowHelper.ThrowArgument_ConcatenateTooFewTensors(); - object IReadOnlyTensor.this[params scoped ReadOnlySpan indexes] => this[indexes]!; + if (dimension < -1 || dimension > tensors[0].Rank) + ThrowHelper.ThrowArgument_InvalidAxis(); - object IReadOnlyTensor.this[params scoped ReadOnlySpan indexes] => this[indexes]!; + // Calculate total space needed. + nint totalLength = 0; + for (int i = 0; i < tensors.Length; i++) + totalLength += tensors[i].FlattenedLength; - object ITensor.this[params scoped ReadOnlySpan indexes] { get => this[indexes]!; set => this[indexes] = (T)value; } - object ITensor.this[params scoped ReadOnlySpan indexes] { get => this[indexes]!; set => this[indexes] = (T)value; } + // If axis != -1, make sure all dimensions except the one to concatenate on match. + if (dimension != -1) + { + nint sumOfAxis = tensors[0].Lengths[dimension]; + for (int i = 1; i < tensors.Length; i++) + { + if (tensors[0].Rank != tensors[i].Rank) + ThrowHelper.ThrowArgument_InvalidConcatenateShape(); + for (int j = 0; j < tensors[0].Rank; j++) + { + if (j != dimension) + { + if (tensors[0].Lengths[j] != tensors[i].Lengths[j]) + ThrowHelper.ThrowArgument_InvalidConcatenateShape(); + } + } + sumOfAxis += tensors[i].Lengths[dimension]; + } - /// - /// Returns a reference to specified element of the Tensor. - /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to FlattenedLength - /// - public ref T this[params scoped ReadOnlySpan indexes] => ref AsTensorSpan()[indexes]; + // Make sure the destination tensor has the correct shape. + nint[] lengths = new nint[tensors[0].Rank]; + tensors[0].Lengths.CopyTo(lengths); + lengths[dimension] = sumOfAxis; - /// - /// Returns a reference to specified element of the Tensor. - /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to FlattenedLength - /// - public ref T this[params scoped ReadOnlySpan indexes] => ref AsTensorSpan()[indexes]; + if (!TensorShape.AreLengthsTheSame(destination.Lengths, lengths)) + ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); + } + Span dstSpan = MemoryMarshal.CreateSpan(ref destination._reference, (int)totalLength); - /// - /// Returns a slice of the Tensor. - /// - /// - /// - /// - /// Thrown when any index is less than 0 or any index is greater than or equal to FlattenedLength - /// - public Tensor this[params ReadOnlySpan ranges] - { - get + if (dimension == 0 || dimension == -1) { - if (ranges.Length != Rank) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return Slice(ranges); + for (int i = 0; i < tensors.Length; i++) + { + TensorOperation.Invoke, T, T>(tensors[i], dstSpan); + dstSpan = dstSpan.Slice((int)tensors[i].FlattenedLength); + } } - set + else { - TensorSpan srcSpan; - if (ranges == ReadOnlySpan.Empty) + Span ranges = TensorOperation.RentedBuffer.CreateUninitialized(destination.Rank, out TensorOperation.RentedBuffer rentedBuffer); + for (int i = 0; i < dimension; i++) { - if (!Lengths.SequenceEqual(value.Lengths)) - ThrowHelper.ThrowArgument_SetSliceNoRange(nameof(value)); - srcSpan = AsTensorSpan().Slice(Lengths); + ranges[i] = 0..1; } - else - srcSpan = AsTensorSpan().Slice(ranges); + for (int i = dimension; i < destination.Rank; i++) + { + ranges[i] = ..; + } + + bool hasMore = true; + while (hasMore) + { + for (int i = 0; i < tensors.Length; i++) + { + Tensor slice = tensors[i].Slice(ranges); + TensorOperation.Invoke, T, T>(slice, dstSpan); + dstSpan = dstSpan.Slice((int)slice.FlattenedLength); + } + hasMore = IncrementIndexes(ranges, dimension, destination.Lengths); + } + rentedBuffer.Dispose(); + } + return ref destination; + } + + private static bool IncrementIndexes(Span ranges, int dimension, ReadOnlySpan lengths) + { + NRange curRange = ranges[dimension - 1]; + ranges[dimension - 1] = new NRange(curRange.Start.Value + 1, curRange.End.Value + 1); - if (!srcSpan.Lengths.SequenceEqual(value.Lengths)) - ThrowHelper.ThrowArgument_SetSliceInvalidShapes(nameof(value)); + for (int i = dimension - 1; i >= 0; i--) + { + if (ranges[i].Start.Value >= lengths[i]) + { + ranges[i] = 0..1; + if (i == 0) + return false; + ranges[i - 1] = new NRange(ranges[i - 1].Start.Value + 1, ranges[i - 1].End.Value + 1); + } + } + return true; + } - value.AsTensorSpan().CopyTo(srcSpan); + private static nint CalculateCopyLength(ReadOnlySpan lengths, int startingAxis) + { + // When starting axis is -1 we want all the data at once same as if starting axis is 0 + if (startingAxis == -1) + startingAxis = 0; + nint length = 1; + for (int i = startingAxis; i < lengths.Length; i++) + { + length *= lengths[i]; } + return length; } + #endregion - /// - /// Returns the specified element of the Tensor. - /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to FlattenedLength - /// - T ITensor, T>.this[params ReadOnlySpan indexes] + #region Create + /// + /// A new tensor with the specified lengths. + public static Tensor Create(scoped ReadOnlySpan lengths, bool pinned = false) + => new Tensor(lengths, strides: [], pinned); + + /// + /// A new tensor with the specified and . + public static Tensor Create(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false) + => new Tensor(lengths, strides, pinned); + + /// + /// A new tensor that uses as its backing buffer. + public static Tensor Create(T[] array) + => new Tensor(array); + + /// + /// A new tensor that uses as its backing buffer and with the specified . + public static Tensor Create(T[] array, scoped ReadOnlySpan lengths) + => new Tensor(array, lengths); + + /// + /// A new tensor that uses as its backing buffer and with the specified and . + public static Tensor Create(T[] array, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => new Tensor(array, lengths, strides); + + /// + /// A new tensor that uses as its backing buffer and with the specified and . + public static Tensor Create(T[] array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => new Tensor(array, start, lengths, strides); + + /// A new tensor that contains elements copied from . + public static Tensor Create(IEnumerable enumerable, bool pinned = false) { - get + if (pinned) { - return this[indexes]; + T[] array = enumerable.ToArray(); + + Tensor tensor = CreateUninitialized([array.Length], pinned); + array.CopyTo(tensor._values); + + return tensor; } - set + else { - this[indexes] = value; + T[] array = enumerable.ToArray(); + return Create(array); } } - /// - /// Returns the specified element of the Tensor. - /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to FlattenedLength - /// - T ITensor, T>.this[params ReadOnlySpan indexes] + /// + /// A new tensor that contains elements copied from and with the specified . + public static Tensor Create(IEnumerable enumerable, scoped ReadOnlySpan lengths, bool pinned = false) + => Create(enumerable, lengths, strides: [], pinned); + + /// + /// A new tensor that contains elements copied from and with the specified and . + public static Tensor Create(IEnumerable enumerable, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false) { - get + if (pinned) { - return this[indexes]; + T[] array = enumerable.ToArray(); + + Tensor tensor = CreateUninitialized(lengths, strides, pinned); + array.CopyTo(tensor._values); + + return tensor; } - set + else { - this[indexes] = value; + T[] array = enumerable.ToArray(); + return Create(array, lengths, strides); } } /// - /// Returns the specified element of the ReadOnlyTensor. + /// Creates a and initializes it with random data in a gaussian normal distribution. /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to FlattenedLength - /// - T IReadOnlyTensor, T>.this[params ReadOnlySpan indexes] => AsReadOnlyTensorSpan()[indexes]; + /// A indicating the lengths of each dimension. + public static Tensor CreateAndFillGaussianNormalDistribution(scoped ReadOnlySpan lengths) + where T : IFloatingPoint + { + return CreateAndFillGaussianNormalDistribution(Random.Shared, lengths); + } /// - /// Returns the specified element of the ReadOnlyTensor. + /// Creates a and initializes it with random data in a gaussian normal distribution. /// - /// - /// - /// - /// Thrown when index less than 0 or index greater than or equal to FlattenedLength - /// - T IReadOnlyTensor, T>.this[params ReadOnlySpan indexes] => AsReadOnlyTensorSpan()[indexes]; + /// + /// A indicating the lengths of each dimension. + public static Tensor CreateAndFillGaussianNormalDistribution(Random random, scoped ReadOnlySpan lengths) + where T : IFloatingPoint + { + Tensor tensor = CreateUninitialized(lengths); + FillGaussianNormalDistribution(tensor, random); + return tensor; + } + /// - /// Returns a slice of the ReadOnlyTensor. + /// Creates a and initializes it with random data uniformly distributed. /// - /// - /// - /// - /// Thrown when any index is less than 0 or any index is greater than or equal to FlattenedLength - /// - Tensor IReadOnlyTensor, T>.this[params ReadOnlySpan ranges] + /// A indicating the lengths of each dimension. + public static Tensor CreateAndFillUniformDistribution(scoped ReadOnlySpan lengths) + where T : IFloatingPoint { - get - { - if (ranges.Length != Rank) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return Slice(ranges); - } + return CreateAndFillUniformDistribution(Random.Shared, lengths); } - // REVIEW: WE WILL WANT THIS CHANGED FROM A BOOL TO SOME FILTER EXPRESSION. /// - /// + /// Creates a and initializes it with random data uniformly distributed. /// - /// - /// - /// - public Tensor this[Tensor filter] + /// + /// A indicating the lengths of each dimension. + public static Tensor CreateAndFillUniformDistribution(Random random, scoped ReadOnlySpan lengths) + where T : IFloatingPoint { - get - { - if (filter.Lengths.Length != Lengths.Length) - throw new ArgumentOutOfRangeException(nameof(filter), "Number of dimensions does not equal the number of dimensions in the span"); - - for (int i = 0; i < filter.Lengths.Length; i++) - { - if (filter.Lengths[i] != Lengths[i]) - ThrowHelper.ThrowArgument_FilterTensorMustEqualTensorLength(); - } - - Span srcSpan = _values; - Span filterSpan = filter._values; - - nint linearLength = TensorHelpers.CountTrueElements(filter); + Tensor tensor = CreateUninitialized(lengths); + FillUniformDistribution(tensor, random); + return tensor; + } - T[] values = _isPinned ? GC.AllocateArray((int)linearLength, _isPinned) : (new T[linearLength]); - int index = 0; - for (int i = 0; i < filterSpan.Length; i++) - { - if (filterSpan[i]) - { - values[i] = srcSpan[index++]; - } - } + /// + public static Tensor CreateUninitialized(scoped ReadOnlySpan lengths, bool pinned = false) + { + TensorShape shape = TensorShape.Create(lengths, strides: []); + T[] array = GC.AllocateUninitializedArray(checked((int)(shape.LinearLength)), pinned); + return new Tensor(array, in shape, pinned); + } - return new Tensor(values, [linearLength], _memoryOffset, _isPinned); - } + /// + public static Tensor CreateUninitialized(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned = false) + { + TensorShape shape = TensorShape.Create(lengths, strides); + T[] values = GC.AllocateUninitializedArray(checked((int)(shape.LinearLength)), pinned); + return new Tensor(values, in shape, pinned); } + #endregion + #region Fill /// - /// Defines an implicit conversion of an array to a . + /// Fills the given with random data in a Gaussian normal distribution. + /// can optionally be provided for seeding. /// - public static implicit operator Tensor(T[] array) => new Tensor(array, [array.Length], memoryOffset: 0); + /// The element type. + /// The destination where the data will be stored. + /// to provide random seeding. Defaults to if not provided. + /// + public static ref readonly TensorSpan FillGaussianNormalDistribution(in TensorSpan destination, Random? random = null) where T : IFloatingPoint + { + Span span = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape.LinearLength); + random ??= Random.Shared; - /// - /// Defines an implicit conversion of a to a . - /// - public static implicit operator TensorSpan(Tensor value) => value.AsTensorSpan(); + for (int i = 0; i < span.Length; i++) + { + double u1 = 1.0 - random.NextDouble(); + double u2 = 1.0 - random.NextDouble(); + span[i] = T.CreateChecked(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); + } - /// - /// Defines an implicit conversion of a to a . - /// - public static implicit operator ReadOnlyTensorSpan(Tensor value) => value.AsReadOnlyTensorSpan(); + return ref destination; + } /// - /// Converts this to a pointing to the same backing memory."/> + /// Fills the given with random data in a uniform distribution. + /// can optionally be provided for seeding. /// - /// - public TensorSpan AsTensorSpan() => new TensorSpan(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_values), _memoryOffset), _lengths, _strides, _values.Length - _memoryOffset); + /// The element type. + /// The destination where the data will be stored. + /// to provide random seeding. Defaults to if not provided. + /// + public static ref readonly TensorSpan FillUniformDistribution(in TensorSpan destination, Random? random = null) where T : IFloatingPoint + { + Span span = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape.LinearLength); + random ??= Random.Shared; + for (int i = 0; i < span.Length; i++) + span[i] = T.CreateChecked(random.NextDouble()); - /// - /// Converts this to a pointing to the same backing memory based on the provided ranges."/> - /// - /// The ranges you want in the . - /// based on the provided ranges. - public TensorSpan AsTensorSpan(params scoped ReadOnlySpan start) => AsTensorSpan().Slice(start); + return ref destination; + } + #endregion + #region Equals /// - /// Converts this to a pointing to the same backing memory based on the provided start locations."/> + /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size + /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> /// - /// The start location you want in the . - /// based on the provided ranges. - public TensorSpan AsTensorSpan(params scoped ReadOnlySpan start) => AsTensorSpan().Slice(start); + /// First to compare. + /// Second to compare. + /// A where the value is true if the elements are equal and false if they are not. + public static Tensor Equals(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IEqualityOperators + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return destination; + } /// - /// Converts this to a pointing to the same backing memory based on the provided start indexes."/> + /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size + /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> /// - /// The ranges you want in the . - /// based on the provided ranges. - public TensorSpan AsTensorSpan(params scoped ReadOnlySpan startIndex) => AsTensorSpan().Slice(startIndex); + /// First to compare. + /// Second to compare. + /// + /// A where the value is true if the elements are equal and false if they are not. + public static ref readonly TensorSpan Equals(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IEqualityOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; + } /// - /// Converts this to a pointing to the same backing memory."/> + /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size + /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> /// - /// - public ReadOnlyTensorSpan AsReadOnlyTensorSpan() => new ReadOnlyTensorSpan(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_values), _memoryOffset), _lengths, _strides, _flattenedLength); + /// First to compare. + /// Second value to compare. + /// A where the value is true if the elements are equal and false if they are not. + public static Tensor Equals(in ReadOnlyTensorSpan x, T y) + where T : IEqualityOperators + { + Tensor destination = CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, bool>(x, y, destination); + return destination; + } /// - /// Converts this to a pointing to the same backing memory based on the provided ranges."/> + /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size + /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> /// - /// The ranges you want in the - /// - public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan start) => AsTensorSpan().Slice(start); + /// First to compare. + /// Second value to compare. + /// + /// A where the value is true if the elements are equal and false if they are not. + public static ref readonly TensorSpan Equals(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IEqualityOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; + } + #endregion + #region EqualsAll /// - /// Converts this to a pointing to the same backing memory based on the provided start locations."/> + /// Compares the elements of two to see if all elements of are equal to . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are eqaul to . /// - /// The start locations you want in the - /// - public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan start) => AsTensorSpan().Slice(start); + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are equal to . + public static bool EqualsAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IEqualityOperators + { + TensorOperation.ValidateCompatibility(x, y); + return TensorOperation.Invoke, T>(x, y); + } /// - /// Converts this to a pointing to the same backing memory based on the provided start indexes."/> + /// Compares the elements of two to see if all elements of are equal to . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are eqaul to . /// - /// The start indexes you want in the - /// - public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndex) => AsTensorSpan().Slice(startIndex); + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are equal to . + public static bool EqualsAll(in ReadOnlyTensorSpan x, T y) + where T : IEqualityOperators => TensorOperation.Invoke, T>(x, y); + #endregion + #region EqualsAny /// - /// Returns a reference to the 0th element of the Tensor. If the Tensor is empty, returns null reference. - /// It can be used for pinning and is required to support the use of Tensor within a fixed statement. + /// Compares the elements of two to see if any elements of are equal to . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are equal to . /// - [EditorBrowsable(EditorBrowsableState.Never)] - public ref T GetPinnableReference() => ref AsTensorSpan().GetPinnableReference(); + /// First to compare. + /// Second to compare against. + /// where the value is true if any elements in are equal to . + public static bool EqualsAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IEqualityOperators + { + // The main loop early exits at the first false condition, so the TensorOperation + // checks x != y and returns false on first equal. This means we want to negate + // whatever the main loop returns as `true` means none are equal. + + TensorOperation.ValidateCompatibility(x, y); + return !TensorOperation.Invoke, T>(x, y); + } /// - /// Returns a reference to the 0th element of the ReadOnlyTensor. If the ReadOnlyTensor is empty, returns null reference. - /// It can be used for pinning and is required to support the use of ReadOnlyTensor within a fixed statement. + /// Compares the elements of two to see if any elements of are equal to . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are equal to . /// - [EditorBrowsable(EditorBrowsableState.Never)] - ref readonly T IReadOnlyTensor, T>.GetPinnableReference() => ref AsReadOnlyTensorSpan().GetPinnableReference(); + /// First to compare. + /// Value to compare against. + /// where the value is true if any elements in are equal to . + public static bool EqualsAny(in ReadOnlyTensorSpan x, T y) + where T : IEqualityOperators => !TensorOperation.Invoke, T>(x, y); + #endregion + #region FilteredUpdate /// - /// Forms a slice out of the given tensor + /// Updates the tensor with the where the is true. /// - /// The ranges for the slice - /// without copying the provided ranges. - public Tensor Slice(params ReadOnlySpan start) + /// Input . + /// Input filter where if the index is true then it will update the . + /// Value to update in the . + public static ref readonly TensorSpan FilteredUpdate(in this TensorSpan tensor, scoped in ReadOnlyTensorSpan filter, T value) { - if (start.Length != Lengths.Length) - throw new ArgumentOutOfRangeException(nameof(start), "Number of dimensions to slice does not equal the number of dimensions in the span"); + if (filter.Lengths.Length != tensor.Lengths.Length) + ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(filter)); - scoped Span lengths; - scoped Span offsets; - nint[]? lengthsArray; - nint[]? offsetsArray; - if (Rank > TensorShape.MaxInlineRank) - { - lengthsArray = ArrayPool.Shared.Rent(Rank); - lengths = lengthsArray.AsSpan(0, Rank); + Span srcSpan = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape.LinearLength); + Span filterSpan = MemoryMarshal.CreateSpan(ref filter._reference, (int)tensor._shape.LinearLength); - offsetsArray = ArrayPool.Shared.Rent(Rank); - offsets = offsetsArray.AsSpan(0, Rank); - } - else + for (int i = 0; i < filterSpan.Length; i++) { - lengths = stackalloc nint[Rank]; - offsets = stackalloc nint[Rank]; - - lengthsArray = null; - offsetsArray = null; + if (filterSpan[i]) + { + srcSpan[i] = value; + } } - lengths.Clear(); - offsets.Clear(); - for (int i = 0; i < start.Length; i++) - { - (offsets[i], lengths[i]) = start[i].GetOffsetAndLength(Lengths[i]); - } + return ref tensor; + } + + /// + /// Updates the tensor with the where the is true. + /// If dimensions are not the same an exception is thrown. + /// + /// Input . + /// Input filter where if the index is true then it will update the . + /// Values to update in the . + public static ref readonly TensorSpan FilteredUpdate(in this TensorSpan tensor, scoped in ReadOnlyTensorSpan filter, scoped in ReadOnlyTensorSpan values) + { + if (filter.Lengths.Length != tensor.Lengths.Length) + ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(filter)); + if (values.Rank != 1) + ThrowHelper.ThrowArgument_1DTensorRequired(nameof(values)); - // When we have an empty Tensor and someone wants to slice all of it, we should return an empty Tensor. - // FlattenedLength is computed everytime so using a local to cache the value. - nint flattenedLength = FlattenedLength; - int memoryOffset = 0; + Span dstSpan = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape.LinearLength); + Span filterSpan = MemoryMarshal.CreateSpan(ref filter._reference, (int)tensor._shape.LinearLength); + Span valuesSpan = MemoryMarshal.CreateSpan(ref values._reference, (int)values._shape.LinearLength); - if (flattenedLength != 0) + int index = 0; + for (int i = 0; i < filterSpan.Length; i++) { - for (int i = 0; i < offsets.Length; i++) + if (filterSpan[i]) { - memoryOffset += (int)(Strides[i] * offsets[i]); + dstSpan[i] = valuesSpan[index++]; } } - if ((memoryOffset >= _values.Length || memoryOffset < 0) && flattenedLength != 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - - Tensor toReturn = new Tensor(_values, lengths, Strides, memoryOffset, _isPinned); + return ref tensor; + } + #endregion - if (offsetsArray != null) - ArrayPool.Shared.Return(offsetsArray); - if (lengthsArray != null) - ArrayPool.Shared.Return(lengthsArray); + #region GreaterThan + /// + /// Compares the elements of two to see which elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> + /// + /// First to compare. + /// Second to compare. + /// A where the value is true if the elements in are greater than and + /// false if they are not. + public static Tensor GreaterThan(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return destination; + } - return toReturn; + /// + /// Compares the elements of two to see which elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> + /// + /// First to compare. + /// Second to compare. + /// + /// A where the value is true if the elements in are greater than and + /// false if they are not. + public static ref readonly TensorSpan GreaterThan(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; } /// - /// Forms a slice out of the given tensor + /// Compares the elements of a to see which elements are greater than . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> /// - /// The start indexes for the slice - /// without copying the provided ranges. - public Tensor Slice(params ReadOnlySpan start) + /// to compare. + /// to compare against . + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static Tensor GreaterThan(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators { - NRange[] ranges = new NRange[start.Length]; - for (int i = 0; i < start.Length; i++) - { - ranges[i] = new NRange(start[i], new NIndex(0, fromEnd: true)); - } - return Slice(ranges); + Tensor destination = Tensor.Create(x.Lengths, false); + GreaterThan(x, y, destination); + return destination; } /// - /// Forms a slice out of the given tensor + /// Compares the elements of a to see which elements are greater than . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> /// - /// The start indexes for the slice - /// without copying the provided ranges. - public Tensor Slice(params ReadOnlySpan startIndex) + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static ref readonly TensorSpan GreaterThan(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IComparisonOperators { - NRange[] ranges = new NRange[startIndex.Length]; - for (int i = 0; i < startIndex.Length; i++) - { - ranges[i] = new NRange(startIndex[i], new NIndex(0, fromEnd: true)); - } - return Slice(ranges); + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; } /// - /// Clears the contents of this tensor. + /// Compares to see which elements are greater than . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void Clear() => AsTensorSpan().Clear(); + /// to compare. + /// to compare against . + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static Tensor GreaterThan(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => LessThan(y, x); /// - /// Copies the contents of this tensor into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. + /// Compares to see which elements are greater than . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> /// - /// The span to copy items into. - /// - /// Thrown when the destination TensorSpan is shorter than the source Tensor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(scoped TensorSpan destination) => AsTensorSpan().CopyTo(destination); + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static ref readonly TensorSpan GreaterThan(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators => ref LessThan(y, x, destination); + #endregion + #region GreaterThanAll /// - /// Fills the contents of this span with the given value. + /// Compares the elements of two to see if all elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are greater than . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Fill(T value) => AsTensorSpan().Fill(value); + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are greater than . + public static bool GreaterThanAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y); + return TensorOperation.Invoke, T>(x, y); + } /// - /// Fills the contents of this span with the given value. + /// Compares the elements of two to see if all elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are greater than . /// - public void Fill(object value) => Fill(value is T t ? t : throw new ArgumentException($"Cannot convert {value} to {typeof(T)}")); + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are greater than . + public static bool GreaterThanAll(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => TensorOperation.Invoke, T>(x, y); /// - /// Copies the contents of this tensor into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. + /// Compares the elements of two to see if all elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are greater than . /// - /// The span to copy items into. - /// If the destination span is shorter than the source tensor, this method - /// return false and no data is written to the destination. - public bool TryCopyTo(scoped TensorSpan destination) => AsTensorSpan().TryCopyTo(destination); + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are greater than . + public static bool GreaterThanAll(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => LessThanAll(y, x); + #endregion + #region GreaterThanAny /// - /// Flattens the contents of this Tensor into the provided . + /// Compares the elements of two to see if any elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are greater than . /// - /// The span to copy items into. - public void FlattenTo(scoped Span destination) => AsTensorSpan().FlattenTo(destination); + /// First to compare. + /// Second to compare against. + /// where the value is true if any elements in are greater than . + public static bool GreaterThanAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so the TensorOperation + // checks !(x > y) and returns false on first equal. This means we want to negate + // whatever the main loop returns as `true` means none are equal. + + TensorOperation.ValidateCompatibility(x, y); + return !TensorOperation.Invoke, T>(x, y); + } /// - /// Flattens the contents of this Tensor into the provided . + /// Compares the elements of two to see if any elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are greater than . /// - /// The span to copy items into. - public bool TryFlattenTo(scoped Span destination) => AsTensorSpan().TryFlattenTo(destination); + /// First to compare. + /// Value to compare against. + /// where the value is true if any elements in are greater than . + public static bool GreaterThanAny(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => !TensorOperation.Invoke, T>(x, y); - // IEnumerable /// - /// Gets an for the . + /// Compares the elements of two to see if any elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are greater than . /// - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// First to compare. + /// Value to compare against. + /// where the value is true if any elements in are greater than . + public static bool GreaterThanAny(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => LessThanAny(y, x); + #endregion - // IEnumerable + #region GreaterThanOrEqual /// - /// Gets an for the . + /// Compares the elements of two to see which elements of are greater than or equal to . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> /// - /// - public IEnumerator GetEnumerator() => new Enumerator(this); + /// First to compare. + /// Second to compare. + /// A where the value is true if the elements in are greater than and + /// false if they are not. + public static Tensor GreaterThanOrEqual(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return destination; + } /// - /// Gets an for the ."/> + /// Compares the elements of two to see which elements of are greater than or equal to . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> /// - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// First to compare. + /// Second to compare. + /// + /// A where the value is true if the elements in are greater than and + /// false if they are not. + public static ref readonly TensorSpan GreaterThanOrEqual(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; + } - private struct Enumerator : IEnumerator + /// + /// Compares the elements of a to see which elements are greater than or equal to . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static Tensor GreaterThanOrEqual(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators { - /// The span being enumerated. - private readonly Tensor _tensor; - /// - /// - /// - private nint[] _curIndices; - /// The total item count. - private nint _items; + Tensor destination = Tensor.Create(x.Lengths, false); + GreaterThanOrEqual(x, y, destination); + return destination; + } - /// Initialize the enumerator. - /// The tensor to enumerate. - internal Enumerator(Tensor tensor) - { - _tensor = tensor; - _items = -1; - _curIndices = new nint[_tensor.Rank]; - _curIndices[_tensor.Rank - 1] = -1; - } + /// + /// Compares the elements of a to see which elements are greater than or equal to . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static ref readonly TensorSpan GreaterThanOrEqual(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; + } - /// Advances the enumerator to the next element of the span. - public bool MoveNext() - { - TensorSpanHelpers.AdjustIndexes(_tensor.Rank - 1, 1, ref _curIndices, _tensor.Lengths); + /// + /// Compares to see which elements are greater than or equal to . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static Tensor GreaterThanOrEqual(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => LessThanOrEqual(y, x); - _items++; - return _items < _tensor.FlattenedLength; - } + /// + /// Compares to see which elements are greater than or equal to . + /// It returns a where the value is true if the elements in are greater than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are greater than + /// and false if they are not. + public static ref readonly TensorSpan GreaterThanOrEqual(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators => ref LessThanOrEqual(y, x, destination); + #endregion - /// - /// Resets the enumerator to the beginning of the span. - /// - public void Reset() - { - Array.Clear(_curIndices); - _curIndices[_tensor.Rank - 1] = -1; - } + #region GreaterThanOrEqualAll + /// + /// Compares the elements of two to see if all elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are greater than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are greater than . + public static bool GreaterThanOrEqualAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y); + return TensorOperation.Invoke, T>(x, y); + } - /// - /// - /// - public void Dispose() - { + /// + /// Compares the elements of two to see if all elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are greater than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are greater than . + public static bool GreaterThanOrEqualAll(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => TensorOperation.Invoke, T>(x, y); - } + /// + /// Compares the elements of two to see if all elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are greater than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are greater than . + public static bool GreaterThanOrEqualAll(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => LessThanOrEqualAll(y, x); + #endregion - /// - /// Current T value of the - /// - T IEnumerator.Current => _tensor[_curIndices]; + #region GreaterThanOrEqualAny + /// + /// Compares the elements of two to see if any elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are greater than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if any elements in are greater than . + public static bool GreaterThanOrEqualAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so the TensorOperation + // checks !(x >= y) and returns false on first equal. This means we want to negate + // whatever the main loop returns as `true` means none are equal. - /// - /// Current of the - /// - object? IEnumerator.Current => _tensor[_curIndices]; + TensorOperation.ValidateCompatibility(x, y); + return !TensorOperation.Invoke, T>(x, y); } - // REVIEW: PENDING API REVIEW TO DETERMINE IMPLEMENTATION /// - /// Gets the hash code for the . + /// Compares the elements of two to see if any elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are greater than . /// - /// The hash code of the tensor. - /// In all cases. - public override int GetHashCode() + /// First to compare. + /// Value to compare against. + /// where the value is true if any elements in are greater than . + public static bool GreaterThanOrEqualAny(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => !TensorOperation.Invoke, T>(x, y); + + /// + /// Compares the elements of two to see if any elements of are greater than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are greater than . + /// + /// First to compare. + /// Value to compare against. + /// where the value is true if any elements in are greater than . + public static bool GreaterThanOrEqualAny(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => LessThanOrEqualAny(y, x); + #endregion + + #region LessThan + /// + /// Compares the elements of two to see which elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// First to compare. + /// Second to compare. + /// A where the value is true if the elements in are less than and + /// false if they are not. + public static Tensor LessThan(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators { - throw new NotImplementedException(); + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return destination; } /// - /// Get a string representation of the tensor. + /// Compares the elements of two to see which elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> /// - private void ToMetadataString(StringBuilder sb) + /// First to compare. + /// Second to compare. + /// + /// A where the value is true if the elements in are less than and + /// false if they are not. + public static ref readonly TensorSpan LessThan(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators { - sb.Append('['); - - for (int i = 0; i < Rank; i++) - { - sb.Append(Lengths[i]); - if (i + 1 < Rank) - sb.Append('x'); - } - - sb.Append($"], type = {typeof(T)}, isPinned = {IsPinned}"); + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; } /// - /// Creates a representation of the ."/> + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> /// - /// Maximum Length of each dimension - /// A representation of the - public string ToString(params ReadOnlySpan maximumLengths) + /// to compare. + /// to compare against . + /// where the value is true if the elements in are less than + /// and false if they are not. + public static Tensor LessThan(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators { - if (maximumLengths.IsEmpty) - { - maximumLengths = Rank <= TensorShape.MaxInlineRank ? stackalloc nint[Rank] : new nint[Rank]; - } - - var sb = new StringBuilder(); - ToMetadataString(sb); - sb.AppendLine("{"); - ((ReadOnlyTensorSpan)AsTensorSpan()).ToString(sb, maximumLengths); - sb.AppendLine("}"); - return sb.ToString(); + Tensor destination = Tensor.Create(x.Lengths, false); + LessThan(x, y, destination); + return destination; } /// - /// Pins and gets a to the backing memory. + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> /// - /// A which has pinned the backing memory. - public MemoryHandle GetPinnedHandle() + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are less than + /// and false if they are not. + public static ref readonly TensorSpan LessThan(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IComparisonOperators { - GCHandle handle = GCHandle.Alloc(_values, GCHandleType.Pinned); - unsafe - { - return new MemoryHandle(Unsafe.AsPointer(ref GetPinnableReference()), handle); - } + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; + } + + /// + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// where the value is true if the elements in are less than + /// and false if they are not. + public static Tensor LessThan(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => GreaterThan(y, x); + + /// + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are less than + /// and false if they are not. + public static ref readonly TensorSpan LessThan(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators => ref GreaterThan(y, x, destination); + #endregion + + #region LessThanAll + /// + /// Compares the elements of two to see if all elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are less than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are less than . + public static bool LessThanAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y); + return TensorOperation.Invoke, T>(x, y); + } + + /// + /// Compares the elements of two to see if all elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are less than . + /// + /// First to compare. + /// Second value to compare against. + /// where the value is true if all elements in are less than . + public static bool LessThanAll(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => TensorOperation.Invoke, T>(x, y); + + /// + /// Compares the elements of two to see if all elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are less than . + /// + /// First value to compare. + /// Second value to compare against. + /// where the value is true if all elements in are less than . + public static bool LessThanAll(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => GreaterThanAll(y, x); + #endregion + + #region LessThanAny + /// + /// Compares the elements of two to see if any elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are less than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if any elements in are less than . + public static bool LessThanAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so the TensorOperation + // checks !(x < y) and returns false on first equal. This means we want to negate + // whatever the main loop returns as `true` means none are equal. + + TensorOperation.ValidateCompatibility(x, y); + return !TensorOperation.Invoke, T>(x, y); + } + + /// + /// Compares the elements of two to see if any elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are less than . + /// + /// First to compare. + /// Second value to compare against. + /// where the value is true if any elements in are less than . + public static bool LessThanAny(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => !TensorOperation.Invoke, T>(x, y); + + /// + /// Compares the elements of two to see if any elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are less than . + /// + /// First value to compare. + /// Second value to compare against. + /// where the value is true if any elements in are less than . + public static bool LessThanAny(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => GreaterThanAny(y, x); + #endregion + + #region LessThanOrEqual + /// + /// Compares the elements of two to see which elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// First to compare. + /// Second to compare. + /// A where the value is true if the elements in are less than and + /// false if they are not. + public static Tensor LessThanOrEqual(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return destination; + } + + /// + /// Compares the elements of two to see which elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// First to compare. + /// Second to compare. + /// + /// A where the value is true if the elements in are less than and + /// false if they are not. + public static ref readonly TensorSpan LessThanOrEqual(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; + } + + /// + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// where the value is true if the elements in are less than + /// and false if they are not. + public static Tensor LessThanOrEqual(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators + { + Tensor destination = Tensor.Create(x.Lengths, false); + LessThanOrEqual(x, y, destination); + return destination; + } + + /// + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are less than + /// and false if they are not. + public static ref readonly TensorSpan LessThanOrEqual(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, bool>(x, y, destination); + return ref destination; + } + + /// + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// where the value is true if the elements in are less than + /// and false if they are not. + public static Tensor LessThanOrEqual(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => GreaterThanOrEqual(y, x); + + /// + /// Compares the elements of a to see which elements are less than . + /// It returns a where the value is true if the elements in are less than + /// and false if they are not."/> + /// + /// to compare. + /// to compare against . + /// + /// where the value is true if the elements in are less than + /// and false if they are not. + public static ref readonly TensorSpan LessThanOrEqual(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IComparisonOperators => ref GreaterThanOrEqual(y, x, destination); + #endregion + + #region LessThanOrEqualAll + /// + /// Compares the elements of two to see if all elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are less than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if all elements in are less than . + public static bool LessThanOrEqualAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + TensorOperation.ValidateCompatibility(x, y); + return TensorOperation.Invoke, T>(x, y); + } + + /// + /// Compares the elements of two to see if all elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are less than . + /// + /// First to compare. + /// Second value to compare against. + /// where the value is true if all elements in are less than . + public static bool LessThanOrEqualAll(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => TensorOperation.Invoke, T>(x, y); + + /// + /// Compares the elements of two to see if all elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if all elements in are less than . + /// + /// First value to compare. + /// Second value to compare against. + /// where the value is true if all elements in are less than . + public static bool LessThanOrEqualAll(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => GreaterThanOrEqualAll(y, x); + #endregion + + #region LessThanOrEqualAny + /// + /// Compares the elements of two to see if any elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are less than . + /// + /// First to compare. + /// Second to compare against. + /// where the value is true if any elements in are less than . + public static bool LessThanOrEqualAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so the TensorOperation + // checks !(x <= y) and returns false on first equal. This means we want to negate + // whatever the main loop returns as `true` means none are equal. + + TensorOperation.ValidateCompatibility(x, y); + return !TensorOperation.Invoke, T>(x, y); + } + + /// + /// Compares the elements of two to see if any elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are less than . + /// + /// First to compare. + /// Second value to compare against. + /// where the value is true if any elements in are less than . + public static bool LessThanOrEqualAny(in ReadOnlyTensorSpan x, T y) + where T : IComparisonOperators => !TensorOperation.Invoke, T>(x, y); + + /// + /// Compares the elements of two to see if any elements of are less than . + /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. + /// It returns a where the value is true if any elements in are less than . + /// + /// First value to compare. + /// Second value to compare against. + /// where the value is true if any elements in are less than . + public static bool LessThanOrEqualAny(T x, in ReadOnlyTensorSpan y) + where T : IComparisonOperators => GreaterThanOrEqualAny(y, x); + #endregion + + #region Permute + + /// + /// Swaps the dimensions of the tensor according to the parameter. + /// If is a 1D tensor, it will return . Otherwise it creates a new + /// with the new axis ordering by allocating new memory. + /// + /// Input + /// with the new axis ordering. + public static Tensor PermuteDimensions(this Tensor tensor, ReadOnlySpan dimensions) + { + if (tensor.Rank == 1) + { + return tensor; + } + else + { + scoped Span newLengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer lengthsRentedBuffer); + scoped Span newStrides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesRentedBuffer); + scoped Span newLinearOrder = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer linearOrderRentedBuffer); + + Tensor outTensor; + + if (dimensions.IsEmpty) + { + for (int i = 0; i < tensor.Rank; i++) + { + newLengths[i] = tensor.Lengths[tensor.Rank - 1 - i]; + newStrides[i] = tensor.Strides[tensor.Rank - 1 - i]; + newLinearOrder[i] = tensor._shape.LinearRankOrder[tensor.Rank - 1 - i]; + } + } + else + { + if (dimensions.Length != tensor.Lengths.Length) + ThrowHelper.ThrowArgument_PermuteAxisOrder(); + + for (int i = 0; i < dimensions.Length; i++) + { + newLengths[i] = tensor.Lengths[dimensions[i]]; + newStrides[i] = tensor.Strides[dimensions[i]]; + newLinearOrder[i] = tensor._shape.LinearRankOrder[dimensions[i]]; + } + } + outTensor = new Tensor(tensor._values, tensor._start, newLengths, newStrides, newLinearOrder); + + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + linearOrderRentedBuffer.Dispose(); + + return outTensor; + } + } + #endregion + + #region Reshape + /// + /// Reshapes the tensor to the specified . If one of the lengths is -1, it will be calculated automatically. + /// Does not change the length of the underlying memory nor does it allocate new memory. If the new shape is not compatible with the old shape, + /// an exception is thrown. + /// + /// you want to reshape. + /// with the new dimensions. + public static Tensor Reshape(this Tensor tensor, ReadOnlySpan lengths) + { + if (tensor.Lengths.SequenceEqual(lengths)) + return tensor; + + if (!tensor.IsContiguousAndDense && !tensor.Strides.Contains(0)) + { + ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); + } + + nint[] newLengths = lengths.ToArray(); + // Calculate wildcard info. + if (lengths.Contains(-1)) + { + if (lengths.Count(-1) > 1) + ThrowHelper.ThrowArgument_OnlyOneWildcard(); + nint tempTotal = tensor.FlattenedLength; + for (int i = 0; i < lengths.Length; i++) + { + if (lengths[i] != -1) + { + tempTotal /= lengths[i]; + } + } + newLengths[lengths.IndexOf(-1)] = tempTotal; + } + + nint tempLinear = TensorPrimitives.Product(newLengths); + if (tempLinear != tensor.FlattenedLength) + ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); + + nint[] strides; + + // If all our strides are 0 we can reshape however we like and keep all new strides at 0 + if (!tensor.Strides.ContainsAnyExcept(0)) + { + strides = new nint[newLengths.Length]; + } + // If we contain a 0 stride we can only add dimensions of length 1. + else if (tensor.Strides.Contains(0)) + { + List origStrides = new List(tensor.Strides.ToArray()); + int lengthOffset = 0; + for (int i = 0; i < newLengths.Length; i++) + { + if (lengthOffset < tensor.Rank && newLengths[i] == tensor.Lengths[lengthOffset]) + lengthOffset++; + else if (newLengths[i] == 1) + { + if (lengthOffset == tensor.Rank) + origStrides.Add(tensor.Strides[lengthOffset - 1]); + else + origStrides.Insert(i, tensor.Strides[i] * tensor.Lengths[i]); + } + else + ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); + } + strides = origStrides.ToArray(); + } + else + strides = []; + + return new Tensor(tensor._values, tensor._start, lengths, strides); + } + + /// + /// Reshapes the tensor to the specified . If one of the lengths is -1, it will be calculated automatically. + /// Does not change the length of the underlying memory nor does it allocate new memory. If the new shape is not compatible with the old shape, + /// an exception is thrown. + /// + /// you want to reshape. + /// with the new dimensions. + public static TensorSpan Reshape(in this TensorSpan tensor, scoped ReadOnlySpan lengths) + { + if (tensor.Lengths.SequenceEqual(lengths)) + return tensor; + + if (!tensor.IsContiguousAndDense && !tensor.Strides.Contains(0)) + { + ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); + } + + nint[] newLengths = lengths.ToArray(); + // Calculate wildcard info. + if (lengths.Contains(-1)) + { + if (lengths.Count(-1) > 1) + ThrowHelper.ThrowArgument_OnlyOneWildcard(); + nint tempTotal = tensor.FlattenedLength; + for (int i = 0; i < lengths.Length; i++) + { + if (lengths[i] != -1) + { + tempTotal /= lengths[i]; + } + } + newLengths[lengths.IndexOf(-1)] = tempTotal; + + } + + nint tempLinear = TensorPrimitives.Product(newLengths); + if (tempLinear != tensor.FlattenedLength) + ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); + + nint[] strides; + + // If all our strides are 0 we can reshape however we like and keep all new strides at 0 + if (!tensor.Strides.ContainsAnyExcept(0)) + { + strides = new nint[newLengths.Length]; + } + // If we contain a 0 stride we can only add dimensions of length 1. + else if (tensor.Strides.Contains(0)) + { + List origStrides = new List(tensor.Strides.ToArray()); + int lengthOffset = 0; + for (int i = 0; i < newLengths.Length; i++) + { + if (lengthOffset < tensor.Rank && newLengths[i] == tensor.Lengths[lengthOffset]) + { + lengthOffset++; + } + else if (newLengths[i] == 1) + { + if (lengthOffset == tensor.Rank) + origStrides.Add(tensor.Strides[lengthOffset - 1]); + else + origStrides.Insert(i, tensor.Strides[i] * tensor.Lengths[i]); + } + else + ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); + } + strides = origStrides.ToArray(); + } + else + strides = []; + + TensorSpan output = new TensorSpan(ref tensor._reference, tensor._shape.LinearLength, newLengths, strides); + return output; + } + + /// + /// Reshapes the tensor to the specified . If one of the lengths is -1, it will be calculated automatically. + /// Does not change the length of the underlying memory nor does it allocate new memory. If the new shape is not compatible with the old shape, + /// an exception is thrown. + /// + /// you want to reshape. + /// with the new dimensions. + public static ReadOnlyTensorSpan Reshape(in this ReadOnlyTensorSpan tensor, scoped ReadOnlySpan lengths) + { + if (tensor.Lengths.SequenceEqual(lengths)) + return tensor; + + if (!tensor.IsContiguousAndDense && !tensor.Strides.Contains(0)) + { + ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); + } + + nint[] newLengths = lengths.ToArray(); + // Calculate wildcard info. + if (lengths.Contains(-1)) + { + if (lengths.Count(-1) > 1) + ThrowHelper.ThrowArgument_OnlyOneWildcard(); + nint tempTotal = tensor.FlattenedLength; + for (int i = 0; i < lengths.Length; i++) + { + if (lengths[i] != -1) + { + tempTotal /= lengths[i]; + } + } + newLengths[lengths.IndexOf(-1)] = tempTotal; + + } + + nint tempLinear = TensorPrimitives.Product(newLengths); + if (tempLinear != tensor.FlattenedLength) + ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); + + nint[] strides; + + // If all our strides are 0 we can reshape however we like and keep all new strides at 0 + if (!tensor.Strides.ContainsAnyExcept(0)) + { + strides = new nint[newLengths.Length]; + } + // If we contain a 0 stride we can only add dimensions of length 1. + else if (tensor.Strides.Contains(0)) + { + List origStrides = new List(tensor.Strides.ToArray()); + int lengthOffset = 0; + for (int i = 0; i < newLengths.Length; i++) + { + if (lengthOffset < tensor.Rank && newLengths[i] == tensor.Lengths[lengthOffset]) + lengthOffset++; + else if (newLengths[i] == 1) + { + if (lengthOffset == tensor.Rank) + origStrides.Add(tensor.Strides[lengthOffset - 1]); + else + origStrides.Insert(i, tensor.Strides[i] * tensor.Lengths[i]); + } + else + ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); + } + strides = origStrides.ToArray(); + } + else + strides = []; + + ReadOnlyTensorSpan output = new ReadOnlyTensorSpan(ref tensor._reference, tensor._shape.LinearLength, newLengths, strides); + return output; + } + #endregion + + #region Resize + /// + /// Creates a new , allocates new memory, and copies the data from . If the final shape is smaller all data after + /// that point is ignored. + /// + /// Input . + /// of the desired new shape. + public static Tensor Resize(Tensor tensor, ReadOnlySpan lengths) + { + nint newSize = TensorPrimitives.Product(lengths); + T[] values = tensor.IsPinned ? GC.AllocateArray((int)newSize) : (new T[newSize]); + Tensor output = Tensor.Create(values, 0, lengths, []); + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref tensor.AsTensorSpan()._reference, tensor._start), (int)tensor._values.Length - tensor._start); + Span ospan = MemoryMarshal.CreateSpan(ref output.AsTensorSpan()._reference, (int)output.FlattenedLength); + if (newSize >= span.Length) + span.CopyTo(ospan); + else + span.Slice(0, ospan.Length).CopyTo(ospan); + + return output; + } + + /// + /// 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. + /// + /// Input . + /// Destination with the desired new shape. + public static void ResizeTo(scoped in Tensor tensor, in TensorSpan destination) + { + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref tensor.AsTensorSpan()._reference, tensor._start), (int)tensor._values.Length - tensor._start); + Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape.LinearLength); + if (ospan.Length >= span.Length) + span.CopyTo(ospan); + else + span.Slice(0, ospan.Length).CopyTo(ospan); + } + + /// + /// 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. + /// + /// Input . + /// Destination with the desired new shape. + public static void ResizeTo(scoped in TensorSpan tensor, in TensorSpan destination) + { + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape.LinearLength); + Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape.LinearLength); + if (ospan.Length >= span.Length) + span.CopyTo(ospan); + else + span.Slice(0, ospan.Length).CopyTo(ospan); + } + + /// + /// 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. + /// + /// Input . + /// Destination with the desired new shape. + public static void ResizeTo(scoped in ReadOnlyTensorSpan tensor, in TensorSpan destination) + { + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape.LinearLength); + Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape.LinearLength); + if (ospan.Length >= span.Length) + span.CopyTo(ospan); + else + span.Slice(0, ospan.Length).CopyTo(ospan); + } + #endregion + + #region Reverse + /// + /// Reverse the order of elements in the . The shape of the tensor is preserved, but the elements are reordered. + /// + /// Input . + public static Tensor Reverse(in ReadOnlyTensorSpan tensor) + { + Tensor output = Tensor.Create(tensor.Lengths); + ReverseDimension(tensor, output, -1); + + return output; + } + + /// + /// Reverse the order of elements in the along the given dimension. The shape of the tensor is preserved, but the elements are reordered. + /// defaults to -1 when not provided, which reverses the entire tensor. + /// + /// Input . + /// dimension along which to reverse over. -1 will reverse over all of the dimensions of the left tensor. + public static Tensor ReverseDimension(in ReadOnlyTensorSpan tensor, int dimension) + { + Tensor output = Tensor.Create(tensor.Lengths); + ReverseDimension(tensor, output, dimension); + + return output; + } + + /// + /// Reverse the order of elements in the . The shape of the tensor is preserved, but the elements are reordered. + /// + /// Input . + /// + public static ref readonly TensorSpan Reverse(scoped in ReadOnlyTensorSpan tensor, in TensorSpan destination) + { + return ref ReverseDimension(tensor, destination, -1); + } + + /// + /// Reverse the order of elements in the along the given axis. The shape of the tensor is preserved, but the elements are reordered. + /// defaults to -1 when not provided, which reverses the entire span. + /// + /// Input . + /// + /// dimension along which to reverse over. -1 will reverse over all of the dimensions of the left tensor. + public static ref readonly TensorSpan ReverseDimension(scoped in ReadOnlyTensorSpan tensor, in TensorSpan destination, int dimension) + { + // When the dimension is -1, its just a straight reverse copy. + if (dimension == -1) + { + TensorOperation.ValidateCompatibility(tensor, destination); + TensorOperation.ReverseInvoke, T, T>(tensor, destination); + } + // With any other dimension, we need to copy the data in reverse order based on the provided dimension. + else + { + TensorOperation.ValidateCompatibility(tensor, destination); + Span srcIndexes = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer srcIndexesRentedBuffer); + Span dstIndexes = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer dstIndexesRentedBuffer); + + for (int i = 0; i < srcIndexes.Length; i++) + { + srcIndexes[i] = NRange.All; + dstIndexes[i] = NRange.All; + } + + for (int i = (int)tensor.Lengths[dimension]; i > 0; i--) + { + srcIndexes[dimension] = new NRange(i - 1, i); + dstIndexes[dimension] = new NRange(tensor.Lengths[dimension] - i, tensor.Lengths[dimension] - i + 1); + TensorOperation.Invoke, T, T>(tensor.Slice(srcIndexes), destination.Slice(dstIndexes)); + } + srcIndexesRentedBuffer.Dispose(); + dstIndexesRentedBuffer.Dispose(); + } + + return ref destination; + } + #endregion + + #region SequenceEqual + /// + /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). + /// + public static bool SequenceEqual(this scoped in TensorSpan tensor, scoped in ReadOnlyTensorSpan other) + where T : IEquatable? + { + return tensor.FlattenedLength == other.FlattenedLength + && tensor._shape.LinearLength == other._shape.LinearLength + && tensor.Lengths.SequenceEqual(other.Lengths) + && MemoryMarshal.CreateReadOnlySpan(in tensor.GetPinnableReference(), (int)tensor._shape.LinearLength).SequenceEqual(MemoryMarshal.CreateReadOnlySpan(in other.GetPinnableReference(), (int)other._shape.LinearLength)); + } + + /// + /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). + /// + public static bool SequenceEqual(this scoped in ReadOnlyTensorSpan tensor, scoped in ReadOnlyTensorSpan other) + where T : IEquatable? + { + return tensor.FlattenedLength == other.FlattenedLength + && tensor._shape.LinearLength == other._shape.LinearLength + && tensor.Lengths.SequenceEqual(other.Lengths) + && MemoryMarshal.CreateReadOnlySpan(in tensor.GetPinnableReference(), (int)tensor._shape.LinearLength).SequenceEqual(MemoryMarshal.CreateReadOnlySpan(in other.GetPinnableReference(), (int)other._shape.LinearLength)); + } + #endregion + + #region SetSlice + /// + /// Sets a slice of the given with the provided for the given + /// + /// Input . + /// The values you want to set in the . + /// The ranges you want to set. + public static Tensor SetSlice(this Tensor tensor, in ReadOnlyTensorSpan values, params ReadOnlySpan ranges) + { + tensor.AsTensorSpan().SetSlice(values, ranges); + return tensor; + } + + /// + /// Sets a slice of the given with the provided for the given + /// + /// Input . + /// The values you want to set in the . + /// The ranges you want to set. + public static ref readonly TensorSpan SetSlice(this in TensorSpan tensor, scoped in ReadOnlyTensorSpan values, params scoped ReadOnlySpan ranges) + { + if (ranges.IsEmpty) + { + values.CopyTo(tensor); + } + else + { + values.CopyTo(tensor.Slice(ranges)); + } + return ref tensor; + } + #endregion + + #region Split + /// + /// Split a into along the given . If the tensor cannot be split + /// evenly on the given an exception is thrown. + /// + /// Input . + /// How many times to split the + /// The axis to split on. + public static Tensor[] Split(scoped in ReadOnlyTensorSpan tensor, int splitCount, nint dimension) + { + if (tensor.Lengths[(int)dimension] % splitCount != 0) + ThrowHelper.ThrowArgument_SplitNotSplitEvenly(); + + Tensor[] outputs = new Tensor[splitCount]; + + nint totalToCopy = tensor.FlattenedLength / splitCount; + + nint[] newShape = tensor.Lengths.ToArray(); + nint splitLength = newShape[dimension] / splitCount; + newShape[dimension] = splitLength; + + scoped Span sliceDims = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer lengthsRentedBuffer); + for (int i = 0; i < sliceDims.Length; i++) + { + sliceDims[i] = NRange.All; + } + nint start = 0; + for (int i = 0; i < outputs.Length; i++) + { + sliceDims[(int)dimension] = new NRange(start, start + splitLength); + T[] values = new T[(int)totalToCopy]; + outputs[i] = new Tensor(values, 0, newShape, [], tensor._shape.LinearRankOrder); + + tensor.Slice(sliceDims).CopyTo(outputs[i]); + start += splitLength; + } + + lengthsRentedBuffer.Dispose(); + return outputs; + } + #endregion + + #region Squeeze + /// + /// Removes all dimensions of length one from the . + /// + /// The to remove all dimensions of length 1. + public static Tensor Squeeze(this Tensor tensor) + { + return SqueezeDimension(tensor, -1); + } + + /// + /// Removes axis of length one from the for the given . + /// If the dimension is not of length one it will throw an exception. + /// + /// The to remove dimension of length 1. + /// The dimension to remove. + public static Tensor SqueezeDimension(this Tensor tensor, int dimension) + { + if (dimension >= tensor.Rank || dimension < -1) + ThrowHelper.ThrowArgument_AxisLargerThanRank(); + + scoped Span lengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer lengthsRentedBuffer); + scoped Span strides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesRentedBuffer); + scoped Span strideOrder = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesOrderRentedBuffer); + int newRank = 0; + int index = 0; + + if (dimension == -1) + { + int removalCount = tensor.Lengths.Count(1); + int removedIndex = 0; + Span removed = TensorOperation.RentedBuffer.CreateUninitialized(removalCount, out TensorOperation.RentedBuffer removedRentedBuffer); + + for (int i = 0; i < tensor.Lengths.Length; i++) + { + if (tensor.Lengths[i] != 1) + { + lengths[index] = tensor.Lengths[i]; + strides[index] = tensor.Strides[i]; + newRank++; + strideOrder[index++] = tensor._shape.LinearRankOrder[i]; + } + else + { + removed[removedIndex++] = tensor._shape.LinearRankOrder[i]; + } + } + SqueezeHelper(removed, strideOrder); + removedRentedBuffer.Dispose(); + } + else + { + if (tensor.Lengths[dimension] != 1) + { + ThrowHelper.ThrowArgument_InvalidSqueezeAxis(); + } + int removed = default; + for (int i = 0; i < tensor.Lengths.Length; i++) + { + if (i != dimension) + { + lengths[index] = tensor.Lengths[i]; + strides[index] = tensor.Strides[i]; + newRank++; + strideOrder[index++] = tensor._shape.LinearRankOrder[i]; + } + else + { + removed = tensor._shape.LinearRankOrder[i]; + } + } + SqueezeHelper(removed, strideOrder); + } + + Tensor output = new Tensor(tensor._values, tensor._start, lengths[..newRank], strides[..newRank], strideOrder[..newRank]); + + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + stridesOrderRentedBuffer.Dispose(); + + return output; + } + + /// + /// Removes all dimensions of length one from the . + /// + /// The to remove all dimensions of length 1. + public static TensorSpan Squeeze(in this TensorSpan tensor) + { + return SqueezeDimension(tensor, -1); + } + + /// + /// Removes axis of length one from the for the given . + /// If the dimension is not of length one it will throw an exception. + /// + /// The to remove dimension of length 1. + /// The dimension to remove. + public static TensorSpan SqueezeDimension(in this TensorSpan tensor, int dimension) + { + if (dimension >= tensor.Rank || dimension < -1) + ThrowHelper.ThrowArgument_AxisLargerThanRank(); + + scoped Span lengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer lengthsRentedBuffer); + scoped Span strides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesRentedBuffer); + scoped Span strideOrder = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesOrderRentedBuffer); + int newRank = 0; + int index = 0; + + if (dimension == -1) + { + int removalCount = tensor.Lengths.Count(1); + int removedIndex = 0; + Span removed = TensorOperation.RentedBuffer.CreateUninitialized(removalCount, out TensorOperation.RentedBuffer removedRentedBuffer); + + for (int i = 0; i < tensor.Lengths.Length; i++) + { + if (tensor.Lengths[i] != 1) + { + lengths[index] = tensor.Lengths[i]; + strides[index] = tensor.Strides[i]; + newRank++; + strideOrder[index++] = tensor._shape.LinearRankOrder[i]; + } + else + { + removed[removedIndex++] = tensor._shape.LinearRankOrder[i]; + } + } + SqueezeHelper(removed, strideOrder); + removedRentedBuffer.Dispose(); + } + else + { + if (tensor.Lengths[dimension] != 1) + { + ThrowHelper.ThrowArgument_InvalidSqueezeAxis(); + } + int removed = default; + for (int i = 0; i < tensor.Lengths.Length; i++) + { + if (i != dimension) + { + lengths[index] = tensor.Lengths[i]; + strides[index] = tensor.Strides[i]; + newRank++; + strideOrder[index++] = tensor._shape.LinearRankOrder[i]; + } + else + { + removed = tensor._shape.LinearRankOrder[i]; + } + } + SqueezeHelper(removed, strideOrder); + } + + TensorSpan output = new TensorSpan(ref tensor._reference, tensor._shape.LinearLength, lengths[..newRank], strides[..newRank], strideOrder[..newRank]); + + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + stridesOrderRentedBuffer.Dispose(); + + return output; + } + + /// + /// Removes all dimensions of length one from the . + /// + /// The to remove all dimensions of length 1. + public static ReadOnlyTensorSpan Squeeze(in this ReadOnlyTensorSpan tensor) + { + return SqueezeDimension(tensor, -1); + } + + /// + /// Removes axis of length one from the for the given . + /// If the dimension is not of length one it will throw an exception. + /// + /// The to remove dimension of length 1. + /// The dimension to remove. + public static ReadOnlyTensorSpan SqueezeDimension(in this ReadOnlyTensorSpan tensor, int dimension) + { + if (dimension >= tensor.Rank || dimension < -1) + ThrowHelper.ThrowArgument_AxisLargerThanRank(); + + scoped Span lengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer lengthsRentedBuffer); + scoped Span strides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesRentedBuffer); + scoped Span strideOrder = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesOrderRentedBuffer); + int newRank = 0; + int index = 0; + + if (dimension == -1) + { + int removalCount = tensor.Lengths.Count(1); + int removedIndex = 0; + Span removed = TensorOperation.RentedBuffer.CreateUninitialized(removalCount, out TensorOperation.RentedBuffer removedRentedBuffer); + + for (int i = 0; i < tensor.Lengths.Length; i++) + { + if (tensor.Lengths[i] != 1) + { + lengths[index] = tensor.Lengths[i]; + strides[index] = tensor.Strides[i]; + newRank++; + strideOrder[index++] = tensor._shape.LinearRankOrder[i]; + } + else + { + removed[removedIndex++] = tensor._shape.LinearRankOrder[i]; + } + } + SqueezeHelper(removed, strideOrder); + removedRentedBuffer.Dispose(); + } + else + { + if (tensor.Lengths[dimension] != 1) + { + ThrowHelper.ThrowArgument_InvalidSqueezeAxis(); + } + int removed = default; + for (int i = 0; i < tensor.Lengths.Length; i++) + { + if (i != dimension) + { + lengths[index] = tensor.Lengths[i]; + strides[index] = tensor.Strides[i]; + newRank++; + strideOrder[index++] = tensor._shape.LinearRankOrder[i]; + } + else + { + removed = tensor._shape.LinearRankOrder[i]; + } + } + SqueezeHelper(removed, strideOrder); + } + + ReadOnlyTensorSpan output = new ReadOnlyTensorSpan(ref tensor._reference, tensor._shape.LinearLength, lengths[..newRank], strides[..newRank], strideOrder[..newRank]); + + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + stridesOrderRentedBuffer.Dispose(); + + return output; + } + + internal static void SqueezeHelper(scoped in Span removed, scoped in Span strideOrder) + { + for (int i = 0; i < strideOrder.Length; i++) + { + for (int j = removed.Length - 1; j >= 0; j--) + { + if (strideOrder[i] > removed[j]) + { + strideOrder[i]--; + } + } + } + } + + internal static void SqueezeHelper(int removed, scoped in Span strideOrder) + { + for (int i = 0; i < strideOrder.Length; i++) + { + if (strideOrder[i] > removed) + { + strideOrder[i]--; + } + } + } + #endregion + + #region Stack + /// + /// Join multiple along a new dimension that is added at position 0. All tensors must have the same shape. + /// + /// Input . + public static Tensor Stack(params ReadOnlySpan> tensors) + { + return StackAlongDimension(0, tensors); + } + + /// + /// Join multiple along a new dimension. The axis parameter specifies the index of the new dimension. All tensors must have the same shape. + /// + /// Input . + /// Index of where the new dimension will be. + public static Tensor StackAlongDimension(int dimension, params ReadOnlySpan> tensors) + { + if (tensors.Length < 2) + ThrowHelper.ThrowArgument_StackTooFewTensors(); + + for (int i = 1; i < tensors.Length; i++) + { + if (!tensors[0].Lengths.SequenceEqual(tensors[i].Lengths)) + ThrowHelper.ThrowArgument_StackShapesNotSame(); + } + + if (dimension < 0) + dimension = tensors[0].Rank - dimension; + + Tensor[] outputs = new Tensor[tensors.Length]; + for (int i = 0; i < tensors.Length; i++) + { + outputs[i] = Tensor.Unsqueeze(tensors[i], dimension); + } + return Tensor.ConcatenateOnDimension(dimension, outputs); + } + + /// + /// Join multiple along a new dimension that is added at position 0. All tensors must have the same shape. + /// + /// Input . + /// + public static ref readonly TensorSpan Stack(scoped in ReadOnlySpan> tensors, in TensorSpan destination) + { + return ref StackAlongDimension(tensors, destination, 0); + } + + /// + /// Join multiple along a new dimension. The axis parameter specifies the index of the new dimension. All tensors must have the same shape. + /// + /// Input . + /// + /// Index of where the new dimension will be. + public static ref readonly TensorSpan StackAlongDimension(scoped ReadOnlySpan> tensors, in TensorSpan destination, int dimension) + { + if (tensors.Length < 2) + ThrowHelper.ThrowArgument_StackTooFewTensors(); + + for (int i = 1; i < tensors.Length; i++) + { + if (!tensors[0].Lengths.SequenceEqual(tensors[i].Lengths)) + ThrowHelper.ThrowArgument_StackShapesNotSame(); + } + + if (dimension < 0) + dimension = tensors[0].Rank - dimension; + + Tensor[] outputs = new Tensor[tensors.Length]; + for (int i = 0; i < tensors.Length; i++) + { + outputs[i] = Tensor.Unsqueeze(tensors[i], dimension); + } + return ref Tensor.ConcatenateOnDimension(dimension, outputs, destination); + } + #endregion + + #region ToString + /// + /// Creates a representation of the ."/> + /// + /// The you want to represent as a string. + /// Maximum Length of each dimension + /// A representation of the + public static string ToString(this in TensorSpan tensor, ReadOnlySpan maximumLengths) + => tensor.AsReadOnlyTensorSpan().ToString(maximumLengths); + + /// + /// Creates a representation of the ."/> + /// + /// + /// The you want to represent as a string. + /// Maximum Length of each dimension + public static string ToString(this in ReadOnlyTensorSpan tensor, ReadOnlySpan maximumLengths) + { + if (maximumLengths.Length != tensor.Rank) + { + ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(tensor)); + } + + StringBuilder sb = new(); + ToString(in tensor, maximumLengths, sb); + return sb.ToString(); + } + + internal static void ToString(in ReadOnlyTensorSpan tensor, ReadOnlySpan maximumLengths, StringBuilder sb, int indentLevel = 0) + { + Debug.Assert(maximumLengths.Length != tensor.Rank); + + sb.Append(' ', indentLevel * 2); + sb.Append('['); + + if (tensor.Rank != 0) + { + nint length = nint.Max(tensor.Lengths[0], maximumLengths[0]); + + if (tensor.Rank != 1) + { + string separator = string.Empty; + + for (nint i = 0; i < length; i++) + { + sb.AppendLine(separator); + + TensorShape tmpShape = TensorShape.Create(tensor.Lengths[1..], tensor.Strides[1..]); + ReadOnlyTensorSpan tmpTensor = new ReadOnlyTensorSpan(ref Unsafe.Add(ref tensor._reference, i * tensor.Strides[0]), tmpShape); + ToString(tmpTensor, maximumLengths[1..], sb, indentLevel + 1); + + separator = ","; + } + + if (length != tensor.Lengths[0]) + { + sb.AppendLine(separator); + sb.Append(' ', indentLevel * 2); + sb.AppendLine("..."); + } + } + else + { + string separator = " "; + + for (nint i = 0; i < length; i++) + { + sb.Append(separator); + sb.Append(Unsafe.Add(ref tensor._reference, i)); + separator = ", "; + } + + if (length != tensor.Lengths[0]) + { + sb.Append(separator); + sb.Append("..."); + } + + sb.Append(separator); + } + } + sb.Append(']'); + } + + /// + /// Creates a representation of the ."/> + /// + /// The you want to represent as a string. + /// Maximum Length of each dimension + /// A representation of the + public static string ToString(this Tensor tensor, ReadOnlySpan maximumLengths) + => tensor.AsReadOnlyTensorSpan().ToString(maximumLengths); + + #endregion + + #region Transpose + /// + /// Swaps the last two dimensions of the tensor. + /// + /// Input . + public static Tensor Transpose(Tensor tensor) + { + if (tensor.Lengths.Length < 2) + ThrowHelper.ThrowArgument_TransposeTooFewDimensions(); + + scoped Span lengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer lengthsRentedBuffer); + scoped Span strides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesRentedBuffer); + scoped Span strideOrder = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank, out TensorOperation.RentedBuffer stridesOrderRentedBuffer); + + tensor.Lengths.CopyTo(lengths); + tensor.Strides.CopyTo(strides); + tensor._shape.LinearRankOrder.CopyTo(strideOrder); + + nint temp = lengths[^1]; + lengths[^1] = lengths[^2]; + lengths[^2] = temp; + + temp = strides[^1]; + strides[^1] = strides[^2]; + strides[^2] = temp; + + int tempOrder = strideOrder[^1]; + strideOrder[^1] = strideOrder[^2]; + strideOrder[^2] = tempOrder; + + Tensor output = new Tensor(tensor._values, tensor._start, lengths, strides, strideOrder); + + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + stridesOrderRentedBuffer.Dispose(); + + return output; + } + #endregion + + #region TryBroadcastTo + /// + /// Broadcast the data from to the smallest broadcastable shape compatible with and stores it in + /// If the shapes are not compatible, false is returned. + /// + /// Input . + /// Destination . + public static bool TryBroadcastTo(this Tensor tensor, in TensorSpan destination) + { + return tensor.AsReadOnlyTensorSpan().TryBroadcastTo(destination); + } + + /// + /// Broadcast the data from to the smallest broadcastable shape compatible with and stores it in + /// If the shapes are not compatible, false is returned. + /// + /// Input . + /// Destination . + public static bool TryBroadcastTo(in this TensorSpan tensor, in TensorSpan destination) + { + return tensor.AsReadOnlyTensorSpan().TryBroadcastTo(destination); + } + + /// + /// Broadcast the data from to the smallest broadcastable shape compatible with and stores it in + /// If the shapes are not compatible, false is returned. + /// + /// Input . + /// Destination . + public static bool TryBroadcastTo(in this ReadOnlyTensorSpan tensor, in TensorSpan destination) + { + TensorOperation.ValidateCompatibility(tensor, destination); + if (!TensorShape.AreCompatible(destination._shape, tensor._shape, false)) + return false; + + BroadcastTo(tensor, destination); + return true; + } + #endregion + + #region Unsqueeze + /// + /// Insert a new dimension of length 1 that will appear at the dimension position. + /// + /// The to add a dimension of length 1. + /// The index of the dimension to add. + public static Tensor Unsqueeze(this Tensor tensor, int dimension) + { + if (dimension > tensor.Lengths.Length) + ThrowHelper.ThrowArgument_AxisLargerThanRank(); + if (dimension < 0) + dimension = tensor.Rank - dimension; + + scoped Span newLengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank + 1, out TensorOperation.RentedBuffer lengthsRentedBuffer); + + tensor.Lengths.Slice(0, dimension).CopyTo(newLengths); + tensor.Lengths.Slice(dimension).CopyTo(newLengths.Slice(dimension + 1)); + newLengths[dimension] = 1; + + Span newStrides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank + 1, out TensorOperation.RentedBuffer stridesRentedBuffer); + if (dimension == tensor.Rank) + { + tensor.Strides.CopyTo(newStrides); + newStrides[dimension] = 0; + } + else + { + tensor.Strides.Slice(0, dimension).CopyTo(newStrides); + tensor.Strides.Slice(dimension).CopyTo(newStrides.Slice(dimension + 1)); + newStrides[dimension] = 0; + } + + Tensor output = new Tensor(tensor._values, tensor._start, newLengths, newStrides); + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + return output; + } + + /// + /// Insert a new dimension of length 1 that will appear at the dimension position. + /// + /// The to add a dimension of length 1. + /// The index of the dimension to add. + public static TensorSpan Unsqueeze(in this TensorSpan tensor, int dimension) + { + if (dimension > tensor.Lengths.Length) + ThrowHelper.ThrowArgument_AxisLargerThanRank(); + if (dimension < 0) + dimension = tensor.Rank - dimension; + + scoped Span newLengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank + 1, out TensorOperation.RentedBuffer lengthsRentedBuffer); + + tensor.Lengths.Slice(0, dimension).CopyTo(newLengths); + tensor.Lengths.Slice(dimension).CopyTo(newLengths.Slice(dimension + 1)); + newLengths[dimension] = 1; + + Span newStrides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank + 1, out TensorOperation.RentedBuffer stridesRentedBuffer); + if (dimension == tensor.Rank) + { + tensor.Strides.CopyTo(newStrides); + newStrides[dimension] = 0; + } + else + { + tensor.Strides.Slice(0, dimension).CopyTo(newStrides); + tensor.Strides.Slice(dimension).CopyTo(newStrides.Slice(dimension + 1)); + newStrides[dimension] = 0; + } + + TensorSpan output = new TensorSpan(ref tensor._reference, tensor._shape.LinearLength, newLengths, newStrides); + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + return output; + } + + /// + /// Insert a new dimension of length 1 that will appear at the dimension position. + /// + /// The to add a dimension of length 1. + /// The index of the dimension to add. + public static ReadOnlyTensorSpan Unsqueeze(in this ReadOnlyTensorSpan tensor, int dimension) + { + if (dimension > tensor.Lengths.Length) + ThrowHelper.ThrowArgument_AxisLargerThanRank(); + if (dimension < 0) + dimension = tensor.Rank - dimension; + + scoped Span newLengths = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank + 1, out TensorOperation.RentedBuffer lengthsRentedBuffer); + + tensor.Lengths.Slice(0, dimension).CopyTo(newLengths); + tensor.Lengths.Slice(dimension).CopyTo(newLengths.Slice(dimension + 1)); + newLengths[dimension] = 1; + + Span newStrides = TensorOperation.RentedBuffer.CreateUninitialized(tensor.Rank + 1, out TensorOperation.RentedBuffer stridesRentedBuffer); + if (dimension == tensor.Rank) + { + tensor.Strides.CopyTo(newStrides); + newStrides[dimension] = 0; + } + else + { + tensor.Strides.Slice(0, dimension).CopyTo(newStrides); + tensor.Strides.Slice(dimension).CopyTo(newStrides.Slice(dimension + 1)); + newStrides[dimension] = 0; + } + + ReadOnlyTensorSpan output = new ReadOnlyTensorSpan(ref tensor._reference, tensor._shape.LinearLength, newLengths, newStrides); + lengthsRentedBuffer.Dispose(); + stridesRentedBuffer.Dispose(); + return output; + } + #endregion + + #region TensorPrimitives + #region Abs + /// + /// Takes the absolute value of each element of the and returns a new with the result. + /// + /// The to take the abs of. + public static Tensor Abs(in ReadOnlyTensorSpan x) + where T : INumberBase + { + Tensor destination = CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the absolute value of each element of the and returns a new with the result. + /// + /// The to take the abs of. + /// The destination. + public static ref readonly TensorSpan Abs(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : INumberBase + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Acos + /// + /// Takes the inverse cosine of each element of the and returns a new with the result. + /// + /// The to take the sin of. + public static Tensor Acos(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse cosine of each element of the and returns a new with the result. + /// + /// The to take the sin of. + /// + public static ref readonly TensorSpan Acos(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Acosh + /// + /// Takes the inverse hyperbolic cosine of each element of the and returns a new with the result. + /// + /// The to take the sin of. + public static Tensor Acosh(in ReadOnlyTensorSpan x) + where T : IHyperbolicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse hyperbolic cosine of each element of the and returns a new with the result. + /// + /// The to take the sin of. + /// + public static ref readonly TensorSpan Acosh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IHyperbolicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region AcosPi + /// + /// Takes the inverse hyperbolic cosine divided by pi of each element of the and returns a new with the result. + /// + /// The to take the sin of. + public static Tensor AcosPi(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse hyperbolic cosine divided by pi of each element of the and returns a new with the result. + /// + /// The to take the sin of. + /// + public static ref readonly TensorSpan AcosPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Add + /// + /// Adds each element of to each element of and returns a new with the result. + /// + /// The of values to add. + /// The second of values to add. + public static Tensor Add(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IAdditionOperators, IAdditiveIdentity + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Adds to each element of and returns a new with the result. + /// + /// The of values to add. + /// The to add to each element of . + public static Tensor Add(in ReadOnlyTensorSpan x, T y) + where T : IAdditionOperators, IAdditiveIdentity + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Adds each element of to each element of and returns a new with the result. + /// + /// The of values to add. + /// The second of values to add. + /// + public static ref readonly TensorSpan Add(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IAdditionOperators, IAdditiveIdentity + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Adds to each element of and returns a new with the result. + /// + /// The of values to add. + /// The to add to each element of . + /// + public static ref readonly TensorSpan Add(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IAdditionOperators, IAdditiveIdentity + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Asin + /// + /// Takes the inverse sin of each element of the and returns a new with the result. + /// + /// The to take the sin of. + public static Tensor Asin(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse sin of each element of the and returns a new with the result. + /// + /// The to take the sin of. + /// + public static ref readonly TensorSpan Asin(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Asinh + /// + /// Takes the inverse hyperbolic sine of each element of the and returns a new with the result. + /// + /// The to take the sin of. + public static Tensor Asinh(in ReadOnlyTensorSpan x) + where T : IHyperbolicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse hyperbolic sine of each element of the and returns a new with the result. + /// + /// The to take the sin of. + /// + public static ref readonly TensorSpan Asinh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IHyperbolicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region AsinPi + /// + /// Takes the inverse hyperbolic sine divided by pi of each element of the and returns a new with the result. + /// + /// The to take the sin of. + public static Tensor AsinPi(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse hyperbolic sine divided by pi of each element of the and returns a new with the result. + /// + /// The to take the sin of. + /// + public static ref readonly TensorSpan AsinPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Atan + /// + /// Takes the arc tangent of each element of the and returns a new with the result. + /// + /// The input + public static Tensor Atan(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the arc tangent of each element of the and returns a new with the result. + /// + /// The input + /// + public static ref readonly TensorSpan Atan(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Atan2 + /// + /// Takes the arc tangent of the two input and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor Atan2(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Takes the arc tangent of the two input and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Atan2(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Takes the arc tangent of the two input and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor Atan2(in ReadOnlyTensorSpan x, T y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Takes the arc tangent of the two input and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Atan2(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Takes the arc tangent of the two input and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor Atan2(T x, in ReadOnlyTensorSpan y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(y.Lengths); + TensorOperation.Invoke, T, T>(y, x, destination); + return destination; + } + + /// + /// Takes the arc tangent of the two input and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Atan2(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(y, x, destination); + return ref destination; + } + #endregion + + #region Atan2Pi + /// + /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor Atan2Pi(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Atan2Pi(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor Atan2Pi(in ReadOnlyTensorSpan x, T y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Atan2Pi(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor Atan2Pi(T x, in ReadOnlyTensorSpan y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(y.Lengths); + TensorOperation.Invoke, T, T>(y, x, destination); + return destination; + } + + /// + /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Atan2Pi(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(y, x, destination); + return ref destination; + } + #endregion + + #region Atanh + /// + /// Takes the inverse hyperbolic tangent of each element of the and returns a new with the result. + /// + /// The input . + public static Tensor Atanh(in ReadOnlyTensorSpan x) + where T : IHyperbolicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse hyperbolic tangent of each element of the and returns a new with the result. + /// + /// The input . + /// + public static ref readonly TensorSpan Atanh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IHyperbolicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region AtanPi + /// + /// Takes the inverse hyperbolic tangent divided by pi of each element of the and returns a new with the result. + /// + /// The input. + public static Tensor AtanPi(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the inverse hyperbolic tangent divided by pi of each element of the and returns a new with the result. + /// + /// The input. + /// + public static ref readonly TensorSpan AtanPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Average + /// + /// Returns the average of the elements in the tensor. + /// + /// The to take the mean of. + /// representing the mean. + public static T Average(scoped in ReadOnlyTensorSpan x) + where T : INumberBase + { + // Get the flattenedLength first so we don't spend time computing if we'll fail due to overflow + + T flattenedLength = T.CreateChecked(x.FlattenedLength); + T sum = Sum(x); + return sum / flattenedLength; + } + #endregion + + #region BitwiseAnd + /// + /// Computes the element-wise bitwise and of the two input and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor BitwiseAnd(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IBitwiseOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Computes the element-wise bitwise and of the two input and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan BitwiseAnd(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Computes the element-wise bitwise and of the two input and returns a new with the result. + /// + /// The left . + /// The second value. + public static Tensor BitwiseAnd(in ReadOnlyTensorSpan x, T y) + where T : IBitwiseOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Computes the element-wise bitwise and of the two input and returns a new with the result. + /// + /// The left . + /// The second value. + /// + public static ref readonly TensorSpan BitwiseAnd(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region BitwiseOr + /// + /// Computes the element-wise bitwise of of the two input and returns a new with the result. + /// + /// The left . + /// The right . + public static Tensor BitwiseOr(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IBitwiseOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Computes the element-wise bitwise of of the two input and returns a new with the result. + /// + /// The left . + /// The right . + /// + public static ref readonly TensorSpan BitwiseOr(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Computes the element-wise bitwise or of the two input and returns a new with the result. + /// + /// The left . + /// The second value. + public static Tensor BitwiseOr(in ReadOnlyTensorSpan x, T y) + where T : IBitwiseOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Computes the element-wise bitwise or of the two input and returns a new with the result. + /// + /// The left . + /// The second value. + /// + public static ref readonly TensorSpan BitwiseOr(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region CubeRoot + /// + /// Computes the element-wise cube root of the input and returns a new with the result. + /// + /// The left . + public static Tensor Cbrt(in ReadOnlyTensorSpan x) + where T : IRootFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Computes the element-wise cube root of the input and returns a new with the result. + /// + /// The left . + /// + public static ref readonly TensorSpan Cbrt(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IRootFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Ceiling + /// + /// Computes the element-wise ceiling of the input and returns a new with the result. + /// + /// The left . + public static Tensor Ceiling(in ReadOnlyTensorSpan x) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Computes the element-wise ceiling of the input and returns a new with the result. + /// + /// The left . + /// + public static ref readonly TensorSpan Ceiling(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region ConvertChecked + /// + /// Copies to a new converting each + /// value to a value. + /// + /// The input . + public static Tensor ConvertChecked(in ReadOnlyTensorSpan source) + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + Tensor destination = Tensor.CreateUninitialized(source.Lengths); + TensorOperation.Invoke, TFrom, TTo>(source, destination); + return destination; + } + + /// + /// Copies to a new converting each + /// value to a value. + /// + /// The input . + /// + public static ref readonly TensorSpan ConvertChecked(scoped in ReadOnlyTensorSpan source, in TensorSpan destination) + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + TensorOperation.ValidateCompatibility(source, destination); + TensorOperation.Invoke, TFrom, TTo>(source, destination); + return ref destination; + } + #endregion + + #region ConvertSaturating + /// + /// Copies to a new converting each + /// value to a value. + /// + /// The input . + public static Tensor ConvertSaturating(in ReadOnlyTensorSpan source) + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + Tensor destination = Tensor.CreateUninitialized(source.Lengths); + TensorOperation.Invoke, TFrom, TTo>(source, destination); + return destination; + } + + /// + /// Copies to a new converting each + /// value to a value. + /// + /// The input . + /// + public static ref readonly TensorSpan ConvertSaturating(scoped in ReadOnlyTensorSpan source, in TensorSpan destination) + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + TensorOperation.ValidateCompatibility(source, destination); + TensorOperation.Invoke, TFrom, TTo>(source, destination); + return ref destination; + } + #endregion + + #region ConvertTruncating + /// + /// Copies to a new converting each + /// value to a value. + /// + /// The input . + public static Tensor ConvertTruncating(in ReadOnlyTensorSpan source) + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + Tensor destination = Tensor.CreateUninitialized(source.Lengths); + TensorOperation.Invoke, TFrom, TTo>(source, destination); + return destination; + } + + /// + /// Copies to a new converting each + /// value to a value. + /// + /// The input . + /// + public static ref readonly TensorSpan ConvertTruncating(scoped in ReadOnlyTensorSpan source, in TensorSpan destination) + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + TensorOperation.ValidateCompatibility(source, destination); + TensorOperation.Invoke, TFrom, TTo>(source, destination); + return ref destination; + } + #endregion + + #region CopySign + /// + /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new tensor with the result. + /// + /// Input . + /// The number with the associated sign. + public static Tensor CopySign(in ReadOnlyTensorSpan x, T sign) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, sign, destination); + return destination; + } + + /// + /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new with the result. + /// + /// Input . + /// The with the associated signs. + public static Tensor CopySign(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan sign) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, sign, destination); + return destination; + } + + /// + /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new tensor with the result. + /// + /// Input . + /// The number with the associated sign. + /// + public static ref readonly TensorSpan CopySign(scoped in ReadOnlyTensorSpan x, T sign, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, sign, destination); + return ref destination; + } + + /// + /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new with the result. + /// + /// Input . + /// The with the associated signs. + /// + public static ref readonly TensorSpan CopySign(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan sign, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(x, sign, destination); + TensorOperation.Invoke, T, T>(x, sign, destination); + return ref destination; + } + #endregion + + #region Cos + /// + /// Takes the cosine of each element of the and returns a new with the result. + /// + /// The to take the cosine of. + public static Tensor Cos(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the cosine of each element of the and returns a new with the result. + /// + /// The to take the cosine of. + /// + public static ref readonly TensorSpan Cos(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Cosh + /// + /// Takes the hyperbolic cosine of each element of the and returns a new with the result. + /// + /// The to take the cosine of. + public static Tensor Cosh(in ReadOnlyTensorSpan x) + where T : IHyperbolicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the hyperbolic cosine of each element of the and returns a new with the result. + /// + /// The to take the cosine of. + /// + public static ref readonly TensorSpan Cosh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IHyperbolicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region CosineSimilarity + /// + /// Compute cosine similarity between and . + /// + /// The first + /// The second + public static T CosineSimilarity(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IRootFunctions + { + TensorOperation.ValidateCompatibility(x, y); + ValueTuple result = (T.AdditiveIdentity, T.AdditiveIdentity, T.AdditiveIdentity); + TensorOperation.Invoke, T, ValueTuple>(x, y, ref result); + return result.Item1 / (T.Sqrt(result.Item2) * T.Sqrt(result.Item3)); + } + #endregion + + #region CosPi + /// Computes the element-wise cosine of the value in the specified tensor that has been multiplied by Pi and returns a new with the results. + /// The input + /// + /// + /// This method effectively computes .CosPi([i]). + /// + /// + /// The angles in x must be in radians. Use or multiply by .Pi/180 to convert degrees to radians. + /// + /// + /// This method may call into the underlying C runtime or employ instructions specific to the current architecture. Exact results may differ between different + /// operating systems or architectures. + /// + /// + public static Tensor CosPi(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise cosine of the value in the specified tensor that has been multiplied by Pi and returns a new with the results. + /// The input + /// + /// + /// + /// This method effectively computes .CosPi([i]). + /// + /// + /// The angles in x must be in radians. Use or multiply by .Pi/180 to convert degrees to radians. + /// + /// + /// This method may call into the underlying C runtime or employ instructions specific to the current architecture. Exact results may differ between different + /// operating systems or architectures. + /// + /// + public static ref readonly TensorSpan CosPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region DegreesToRadians + /// + /// Computes the element-wise conversion of each number of degrees in the specified tensor to radians and returns a new tensor with the results. + /// + /// The input . + public static Tensor DegreesToRadians(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Computes the element-wise conversion of each number of degrees in the specified tensor to radians and returns a new tensor with the results. + /// + /// The input . + /// + public static ref readonly TensorSpan DegreesToRadians(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Distance + /// + /// Computes the distance between two points, specified as non-empty, equal-length tensors of numbers, in Euclidean space. + /// + /// The input . + /// The input . + public static T Distance(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y) + where T : IRootFunctions + { + TensorOperation.ValidateCompatibility(x, y); + T result = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, y, ref result); + return T.Sqrt(result); + } + #endregion + + #region Divide + /// + /// Divides each element of by and returns a new with the result. + /// + /// Input . + /// The divisor + public static Tensor Divide(in ReadOnlyTensorSpan x, T y) + where T : IDivisionOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Divides by each element of and returns a new with the result."/> + /// + /// The value to be divided. + /// The divisor. + public static Tensor Divide(T x, in ReadOnlyTensorSpan y) + where T : IDivisionOperators + { + Tensor destination = Tensor.CreateUninitialized(y.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Divides each element of by its corresponding element in and returns + /// a new with the result. + /// + /// The to be divided. + /// The divisor. + public static Tensor Divide(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IDivisionOperators + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Divides each element of by and returns a new with the result. + /// + /// Input . + /// The divisor + /// + public static ref readonly TensorSpan Divide(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IDivisionOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Divides by each element of and returns a new with the result."/> + /// + /// The value to be divided. + /// The divisor. + /// + public static ref readonly TensorSpan Divide(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IDivisionOperators + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Divides each element of by its corresponding element in and returns + /// a new with the result. + /// + /// The to be divided. + /// The divisor. + /// + public static ref readonly TensorSpan Divide(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IDivisionOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Dot + /// + /// Computes the dot product of two tensors containing numbers. + /// + /// The input . + /// The input . + public static T Dot(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IAdditionOperators, IAdditiveIdentity, IMultiplicativeIdentity, IMultiplyOperators + { + TensorOperation.ValidateCompatibility(x, y); + T result = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, y, ref result); + return result; + } + #endregion + + #region Exp + /// + /// Computes the element-wise result of raising e to the single-precision floating-point number powers in the specified tensor. + /// + /// The input . + public static Tensor Exp(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Computes the element-wise result of raising e to the single-precision floating-point number powers in the specified tensor. + /// + /// The input . + /// + public static ref readonly TensorSpan Exp(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Exp10 + /// + /// Computes the element-wise result of raising 10 to the number powers in the specified tensor. + /// + /// The input . + public static Tensor Exp10(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Computes the element-wise result of raising 10 to the number powers in the specified tensor. + /// + /// The input . + /// + public static ref readonly TensorSpan Exp10(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Exp10M1 + /// Computes the element-wise result of raising 10 to the number powers in the specified tensor, minus one. + /// The input . + public static Tensor Exp10M1(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise result of raising 10 to the number powers in the specified tensor, minus one. + /// The input . + /// + public static ref readonly TensorSpan Exp10M1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Exp2 + /// Computes the element-wise result of raising 2 to the number powers in the specified tensor. + /// The input . + public static Tensor Exp2(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise result of raising 2 to the number powers in the specified tensor. + /// The input . + /// + public static ref readonly TensorSpan Exp2(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Exp2M1 + /// Computes the element-wise result of raising 2 to the number powers in the specified tensor, minus one. + /// The input . + public static Tensor Exp2M1(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise result of raising 2 to the number powers in the specified tensor, minus one. + /// The input . + /// + public static ref readonly TensorSpan Exp2M1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region ExpM1 + /// Computes the element-wise result of raising e to the number powers in the specified tensor, minus 1. + /// The input . + public static Tensor ExpM1(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise result of raising e to the number powers in the specified tensor, minus 1. + /// The input . + /// + public static ref readonly TensorSpan ExpM1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Floor + /// Computes the element-wise floor of numbers in the specified tensor. + /// The input . + public static Tensor Floor(in ReadOnlyTensorSpan x) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise floor of numbers in the specified tensor. + /// The input . + /// + public static ref readonly TensorSpan Floor(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Hypotenuse + /// + /// Computes the element-wise hypotenuse given values from two tensors representing the lengths of the shorter sides in a right-angled triangle. + /// If the shapes are not the same they are broadcast to the smallest compatible shape. + /// + /// Left . + /// Right . + public static Tensor Hypot(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IRootFunctions + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Computes the element-wise hypotenuse given values from two tensors representing the lengths of the shorter sides in a right-angled triangle. + /// If the shapes are not the same they are broadcast to the smallest compatible shape. + /// + /// Left . + /// Right . + /// + public static ref readonly TensorSpan Hypot(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IRootFunctions + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Ieee754Remainder + /// Computes the element-wise remainder of the numbers in the specified tensors. + /// If the shapes are not the same they are broadcast to the smallest compatible shape. + /// Left . + /// Right . + public static Tensor Ieee754Remainder(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise remainder of the numbers in the specified tensors. + /// If the shapes are not the same they are broadcast to the smallest compatible shape. + /// Left . + /// Right . + /// + public static ref readonly TensorSpan Ieee754Remainder(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise remainder of the numbers in the specified tensors. + /// The left . + /// The right . + public static Tensor Ieee754Remainder(in ReadOnlyTensorSpan x, T y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise remainder of the numbers in the specified tensors. + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Ieee754Remainder(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise remainder of the numbers in the specified tensors. + /// The left . + /// The right . + public static Tensor Ieee754Remainder(T x, in ReadOnlyTensorSpan y) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(y.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise remainder of the numbers in the specified tensors. + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Ieee754Remainder(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region ILogB + /// Computes the element-wise integer logarithm of numbers in the specified tensor. + /// The input . + public static Tensor ILogB(in ReadOnlyTensorSpan x) + where T : IFloatingPointIeee754 + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, int>(x, destination); + return destination; + } + + /// Computes the element-wise integer logarithm of numbers in the specified tensor. + /// The input . + /// + public static ref readonly TensorSpan ILogB(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IFloatingPointIeee754 + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, int>(x, destination); + return ref destination; + } + #endregion + + #region IndexOfMax + /// Searches for the index of the largest number in the specified tensor. + /// The input . + public static nint IndexOfMax(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape.LinearLength); + return TensorPrimitives.IndexOfMax(span); + } + + #endregion + + #region IndexOfMaxMagnitude + /// Searches for the index of the number with the largest magnitude in the specified tensor. + /// The input . + public static nint IndexOfMaxMagnitude(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape.LinearLength); + return TensorPrimitives.IndexOfMaxMagnitude(span); + } + #endregion + + #region IndexOfMin + /// Searches for the index of the smallest number in the specified tensor. + /// The input . + public static nint IndexOfMin(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape.LinearLength); + return TensorPrimitives.IndexOfMin(span); + } + #endregion + + #region IndexOfMinMagnitude + /// + /// Searches for the index of the number with the smallest magnitude in the specified tensor. + /// + /// The input . + public static nint IndexOfMinMagnitude(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape.LinearLength); + return TensorPrimitives.IndexOfMinMagnitude(span); + } + #endregion + + #region LeadingZeroCount + /// + /// Computes the element-wise leading zero count of numbers in the specified tensor. + /// + /// The input . + public static Tensor LeadingZeroCount(in ReadOnlyTensorSpan x) + where T : IBinaryInteger + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Computes the element-wise leading zero count of numbers in the specified tensor. + /// + /// The input . + /// + public static ref readonly TensorSpan LeadingZeroCount(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IBinaryInteger + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Log + /// + /// Takes the natural logarithm of each element of the and returns a new with the result. + /// + /// The to take the natural logarithm of. + public static Tensor Log(in ReadOnlyTensorSpan x) + where T : ILogarithmicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the natural logarithm of each element of the and returns a new with the result. + /// + /// The to take the natural logarithm of. + /// + public static ref readonly TensorSpan Log(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + + /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. + /// The first tensor + /// The second tensor + public static Tensor Log(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. + /// The first tensor + /// The second tensor + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan Log(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. + /// The first tensor + /// The second tensor + public static Tensor Log(in ReadOnlyTensorSpan x, T y) + where T : ILogarithmicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. + /// The first tensor + /// The second tensor + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan Log(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Log10 + /// + /// Takes the base 10 logarithm of each element of the and returns a new with the result. + /// + /// The to take the base 10 logarithm of. + public static Tensor Log10(in ReadOnlyTensorSpan x) + where T : ILogarithmicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the base 10 logarithm of each element of the and returns a new with the result. + /// + /// The to take the base 10 logarithm of. + /// + public static ref readonly TensorSpan Log10(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Log10P1 + /// + /// Takes the base 10 logarithm plus 1 of each element of the and returns a new with the result. + /// + /// The to take the base 10 logarithm of. + public static Tensor Log10P1(in ReadOnlyTensorSpan x) + where T : ILogarithmicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the base 10 logarithm plus 1 of each element of the and returns a new with the result. + /// + /// The to take the base 10 logarithm of. + /// + public static ref readonly TensorSpan Log10P1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Log2 + /// + /// Takes the base 2 logarithm of each element of the and returns a new with the result. + /// + /// The to take the base 2 logarithm of. + public static Tensor Log2(in ReadOnlyTensorSpan x) + where T : ILogarithmicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the base 2 logarithm of each element of the and returns a new with the result. + /// + /// The to take the base 2 logarithm of. + /// + public static ref readonly TensorSpan Log2(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Log2P1 + /// + /// Takes the base 2 logarithm plus 1 of each element of the and returns a new with the result. + /// + /// The to take the base 2 logarithm of. + public static Tensor Log2P1(in ReadOnlyTensorSpan x) + where T : ILogarithmicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the base 2 logarithm plus 1 of each element of the and returns a new with the result. + /// + /// The to take the base 2 logarithm of. + /// + public static ref readonly TensorSpan Log2P1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region LogP1 + /// + /// Takes the natural logarithm plus 1 of each element of the and returns a new with the result. + /// + /// The to take the natural logarithm of. + public static Tensor LogP1(in ReadOnlyTensorSpan x) + where T : ILogarithmicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the natural logarithm plus 1 of each element of the and returns a new with the result. + /// + /// The to take the natural logarithm of. + /// + public static ref readonly TensorSpan LogP1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ILogarithmicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Max + /// Searches for the largest number in the specified tensor. + /// The input .. + public static T Max(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor Max(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor output); + TensorOperation.Invoke, T, T>(x, y, output); + return output; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan Max(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor Max(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan Max(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region MaxMagnitude + /// Searches for the number with the largest magnitude in the specified tensor. + /// The input .. + public static T MaxMagnitude(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MaxMagnitude(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MaxMagnitude(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MaxMagnitude(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MaxMagnitude(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region MaxMagnitudeNumber + /// Searches for the number with the largest magnitude in the specified tensor. + /// The input .. + public static T MaxMagnitudeNumber(scoped in ReadOnlyTensorSpan x) + where T : INumberBase + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MaxMagnitudeNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MaxMagnitudeNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MaxMagnitudeNumber(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MaxMagnitudeNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region MaxNumber + /// Searches for the largest number in the specified tensor. + /// The input .. + public static T MaxNumber(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MaxNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MaxNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MaxNumber(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise maximum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MaxNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Min + /// Searches for the largest number in the specified tensor. + /// The input .. + public static T Min(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor Min(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor output); + TensorOperation.Invoke, T, T>(x, y, output); + return output; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan Min(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor Min(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan Min(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region MinMagnitude + /// Searches for the number with the largest magnitude in the specified tensor. + /// The input .. + public static T MinMagnitude(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MinMagnitude(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MinMagnitude(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MinMagnitude(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MinMagnitude(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region MinMagnitudeNumber + /// Searches for the number with the largest magnitude in the specified tensor. + /// The input .. + public static T MinMagnitudeNumber(scoped in ReadOnlyTensorSpan x) + where T : INumberBase + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MinMagnitudeNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MinMagnitudeNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MinMagnitudeNumber(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise number with the largest magnitude in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MinMagnitudeNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region MinNumber + /// Searches for the largest number in the specified tensor. + /// The input .. + public static T MinNumber(scoped in ReadOnlyTensorSpan x) + where T : INumber + { + if (x.IsEmpty) + { + ThrowHelper.ThrowArgument_SpansMustBeNonEmpty(); + } + + T result = x._reference; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MinNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MinNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in y, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + public static Tensor MinNumber(in ReadOnlyTensorSpan x, T y) + where T : INumber + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise Minimum of the numbers in the specified tensors. + /// The first tensor, represented as a span. + /// The second tensor, represented as a span. + /// The destination tensor, represented as a span. + public static ref readonly TensorSpan MinNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : INumber + { + TensorOperation.ValidateCompatibility(in x, in destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Multiply + /// + /// Multiplies each element of with and returns a new with the result. + /// + /// Input + /// value to multiply by. + public static Tensor Multiply(in ReadOnlyTensorSpan x, T y) + where T : IMultiplyOperators, IMultiplicativeIdentity + { + Tensor destination = Tensor.Create(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Multiplies each element of with and returns a new with the result. + /// If the shapes are not the same they are broadcast to the smallest compatible shape. + /// + /// Left for multiplication. + /// Right for multiplication. + public static Tensor Multiply(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IMultiplyOperators, IMultiplicativeIdentity + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Multiplies each element of with and returns a new with the result. + /// + /// Input + /// value to multiply by. + /// + public static ref readonly TensorSpan Multiply(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IMultiplyOperators, IMultiplicativeIdentity + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Multiplies each element of with and returns a new with the result. + /// If the shapes are not the same they are broadcast to the smallest compatible shape. + /// + /// Left for multiplication. + /// Right for multiplication. + /// + public static ref readonly TensorSpan Multiply(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IMultiplyOperators, IMultiplicativeIdentity + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Negate + /// Computes the element-wise negation of each number in the specified tensor. + /// The + public static Tensor Negate(in ReadOnlyTensorSpan x) + where T : IUnaryNegationOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise negation of each number in the specified tensor. + /// The + /// + public static ref readonly TensorSpan Negate(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IUnaryNegationOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Norm + /// + /// Takes the norm of the and returns the result. + /// + /// The to take the norm of. + public static T Norm(scoped in ReadOnlyTensorSpan x) + where T : IRootFunctions + { + T result = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, ref result); + return T.Sqrt(result); + } + #endregion + + #region OnesComplement + /// Computes the element-wise one's complement of numbers in the specified tensor. + /// The + public static Tensor OnesComplement(in ReadOnlyTensorSpan x) + where T : IBitwiseOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise one's complement of numbers in the specified tensor. + /// The + /// + public static ref readonly TensorSpan OnesComplement(scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(y, destination); + return ref destination; + } + #endregion + + #region PopCount + /// Computes the element-wise population count of numbers in the specified tensor. + /// The + public static Tensor PopCount(in ReadOnlyTensorSpan x) + where T : IBinaryInteger + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise population count of numbers in the specified tensor. + /// The + /// + public static ref readonly TensorSpan PopCount(scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IBinaryInteger + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(y, destination); + return ref destination; + } + #endregion + + #region Pow + /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. + /// The input . + /// The second input + public static Tensor Pow(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IPowerFunctions + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. + /// The input . + /// The second input + /// + public static ref readonly TensorSpan Pow(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IPowerFunctions + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. + /// The input . + /// The second input + public static Tensor Pow(in ReadOnlyTensorSpan x, T y) + where T : IPowerFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. + /// The input . + /// The second input + /// + public static ref readonly TensorSpan Pow(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IPowerFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. + /// The input . + /// The second input + public static Tensor Pow(T x, in ReadOnlyTensorSpan y) + where T : IPowerFunctions + { + Tensor destination = Tensor.CreateUninitialized(y.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. + /// The input . + /// The second input + /// + public static ref readonly TensorSpan Pow(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IPowerFunctions + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Product + /// Computes the product of all elements in the specified non-empty tensor of numbers. + /// The input . + public static T Product(scoped in ReadOnlyTensorSpan x) + where T : IMultiplicativeIdentity, IMultiplyOperators + { + T destination = T.MultiplicativeIdentity; + TensorOperation.Invoke, T, T>(x, ref destination); + return destination; + } + #endregion + + #region RadiansToDegrees + /// Computes the element-wise conversion of each number of radians in the specified tensor to degrees. + /// The input . + public static Tensor RadiansToDegrees(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise conversion of each number of radians in the specified tensor to degrees. + /// The input . + /// + public static ref readonly TensorSpan RadiansToDegrees(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Reciprocal + /// Computes the element-wise reciprocal of numbers in the specified tensor. + /// The input . + public static Tensor Reciprocal(in ReadOnlyTensorSpan x) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise reciprocal of numbers in the specified tensor. + /// The input . + /// + public static ref readonly TensorSpan Reciprocal(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region RootN + /// Computes the element-wise n-th root of the values in the specified tensor. + /// The tensor, represented as a span. + /// The degree of the root to be computed, represented as a scalar. + public static Tensor RootN(in ReadOnlyTensorSpan x, int n) + where T : IRootFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, n, destination); + return destination; + } + + /// Computes the element-wise n-th root of the values in the specified tensor. + /// The tensor, represented as a span. + /// The destination tensor, represented as a span. + /// The degree of the root to be computed, represented as a scalar. + public static ref readonly TensorSpan RootN(scoped in ReadOnlyTensorSpan x, int n, in TensorSpan destination) + where T : IRootFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, n, destination); + return ref destination; + } + #endregion + + #region RotateLeft + /// Computes the element-wise rotation left of numbers in the specified tensor by the specified rotation amount. + /// The tensor + /// The number of bits to rotate, represented as a scalar. + /// Destination is too short. + public static Tensor RotateLeft(in ReadOnlyTensorSpan x, int rotateAmount) + where T : IBinaryInteger + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, rotateAmount, destination); + return destination; + } + + /// Computes the element-wise rotation left of numbers in the specified tensor by the specified rotation amount. + /// The tensor + /// The number of bits to rotate, represented as a scalar. + /// + /// Destination is too short. + public static ref readonly TensorSpan RotateLeft(scoped in ReadOnlyTensorSpan x, int rotateAmount, in TensorSpan destination) + where T : IBinaryInteger + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, rotateAmount, destination); + return ref destination; + } + #endregion + + #region RotateRight + /// Computes the element-wise rotation right of numbers in the specified tensor by the specified rotation amount. + /// The tensor + /// The number of bits to rotate, represented as a scalar. + /// Destination is too short. + public static Tensor RotateRight(in ReadOnlyTensorSpan x, int rotateAmount) + where T : IBinaryInteger + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, rotateAmount, destination); + return destination; + } + + /// Computes the element-wise rotation right of numbers in the specified tensor by the specified rotation amount. + /// The tensor + /// The number of bits to rotate, represented as a scalar. + /// + /// Destination is too short. + public static ref readonly TensorSpan RotateRight(scoped in ReadOnlyTensorSpan x, int rotateAmount, in TensorSpan destination) + where T : IBinaryInteger + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, rotateAmount, destination); + return ref destination; + } + #endregion + + #region Round + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + public static Tensor Round(in ReadOnlyTensorSpan x) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + /// + public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + /// + /// + public static Tensor Round(in ReadOnlyTensorSpan x, int digits, MidpointRounding mode) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, Tuple, T>(x, Tuple.Create(digits, mode), destination); + return destination; + } + + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + /// + /// + /// + public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, int digits, MidpointRounding mode, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, Tuple, T>(x, Tuple.Create(digits, mode), in destination); + return ref destination; + } + + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + /// + public static Tensor Round(in ReadOnlyTensorSpan x, int digits) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, Tuple, T>(x, Tuple.Create(digits, MidpointRounding.ToEven), destination); + return destination; + } + + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + /// + /// + public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, int digits, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, Tuple, T>(x, Tuple.Create(digits, MidpointRounding.ToEven), in destination); + return ref destination; + } + + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + /// + public static Tensor Round(in ReadOnlyTensorSpan x, MidpointRounding mode) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, Tuple, T>(x, Tuple.Create(0, mode), destination); + return destination; + } + + /// Computes the element-wise rounding of the numbers in the specified tensor + /// The input . + /// + /// + public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, MidpointRounding mode, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, Tuple, T>(x, Tuple.Create(0, mode), in destination); + return ref destination; + } + #endregion + + #region Sigmoid + /// Computes the element-wise sigmoid function on the specified non-empty tensor of numbers. + /// The input . + public static Tensor Sigmoid(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise sigmoid function on the specified non-empty tensor of numbers. + /// The input . + /// + public static ref readonly TensorSpan Sigmoid(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Sin + /// + /// Takes the sin of each element of the and returns a new with the result. + /// + /// The to take the sin of. + public static Tensor Sin(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the sin of each element of the and returns a new with the result. + /// + /// The to take the sin of. + /// + public static ref readonly TensorSpan Sin(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Sinh + /// Computes the element-wise hyperbolic sine of each radian angle in the specified tensor. + /// The to take the sin of. + public static Tensor Sinh(in ReadOnlyTensorSpan x) + where T : IHyperbolicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise hyperbolic sine of each radian angle in the specified tensor. + /// The to take the sin of. + /// + public static ref readonly TensorSpan Sinh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IHyperbolicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region SinPi + /// Computes the element-wise sine of the value in the specified tensor that has been multiplied by Pi. + /// The to take the sin of. + public static Tensor SinPi(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise sine of the value in the specified tensor that has been multiplied by Pi. + /// The to take the sin of. + /// + public static ref readonly TensorSpan SinPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region SoftMax + /// Computes the softmax function over the specified non-empty tensor of numbers. + /// The to take the sin of. + public static Tensor SoftMax(in ReadOnlyTensorSpan x) + where T : IExponentialFunctions + { + T sumExp = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, ref sumExp); + + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, sumExp, destination); + return destination; + } + + /// Computes the softmax function over the specified non-empty tensor of numbers. + /// The to take the sin of. + /// + public static ref readonly TensorSpan SoftMax(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IExponentialFunctions + { + T sumExp = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, ref sumExp); + + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, sumExp, destination); + return ref destination; + } + #endregion + + #region Sqrt + /// + /// Takes the square root of each element of the and returns a new with the result. + /// + /// The to take the square root of. + public static Tensor Sqrt(in ReadOnlyTensorSpan x) + where T : IRootFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// + /// Takes the square root of each element of the and returns a new with the result. + /// + /// The to take the square root of. + /// + public static ref readonly TensorSpan Sqrt(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IRootFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region StdDev + /// + /// Returns the standard deviation of the elements in the tensor. + /// + /// The to take the standard deviation of. + /// representing the standard deviation. + public static T StdDev(in ReadOnlyTensorSpan x) + where T : IRootFunctions + { + T mean = Average(x); + T result = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, mean, ref result); + T variance = result / T.CreateChecked(x.FlattenedLength); + return T.Sqrt(variance); + + } + #endregion + + #region Subtract + /// + /// Subtracts from each element of and returns a new with the result. + /// + /// The . + /// The to subtract. + public static Tensor Subtract(in ReadOnlyTensorSpan x, T y) + where T : ISubtractionOperators + { + Tensor destination = CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Subtracts each element of from and returns a new with the result. + /// + /// The to be subtracted from. + /// The of values to subtract. + public static Tensor Subtract(T x, in ReadOnlyTensorSpan y) + where T : ISubtractionOperators + { + Tensor destination = CreateUninitialized(y.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Subtracts each element of from and returns a new with the result. + /// + /// The with values to be subtracted from. + /// The with values to subtract. + public static Tensor Subtract(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : ISubtractionOperators + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Subtracts from each element of and returns a new with the result. + /// + /// The with values to be subtracted from. + /// The value to subtract. + /// + public static ref readonly TensorSpan Subtract(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : ISubtractionOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Subtracts each element of from and returns a new with the result. + /// + /// The value to be subtracted from. + /// The values to subtract. + /// + public static ref readonly TensorSpan Subtract(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : ISubtractionOperators + { + TensorOperation.ValidateCompatibility(y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Subtracts each element of from and returns a new with the result. + /// + /// The of values to be subtracted from. + /// The of values to subtract. + /// + public static ref readonly TensorSpan Subtract(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : ISubtractionOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + #endregion + + #region Sum + /// + /// Sums the elements of the specified tensor. + /// + /// Tensor to sum + /// + public static T Sum(scoped in ReadOnlyTensorSpan x) + where T : IAdditionOperators, IAdditiveIdentity + { + T destination = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, ref destination); + return destination; + } + #endregion + + #region SumOfSquares + /// + /// Sums the squared elements of the specified tensor. + /// + /// Tensor to sum squares of + /// + internal static T SumOfSquares(scoped in ReadOnlyTensorSpan x) + where T : IAdditionOperators, IAdditiveIdentity, IMultiplyOperators + { + T result = T.AdditiveIdentity; + TensorOperation.Invoke, T, T>(x, ref result); + return result; + } + #endregion + + #region Tan + /// Computes the element-wise tangent of the value in the specified tensor. + /// The to take the sin of. + public static Tensor Tan(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise tangent of the value in the specified tensor. + /// The to take the sin of. + /// + public static ref readonly TensorSpan Tan(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Tanh + /// Computes the element-wise hyperbolic tangent of each radian angle in the specified tensor. + /// The to take the sin of. + public static Tensor Tanh(in ReadOnlyTensorSpan x) + where T : IHyperbolicFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise hyperbolic tangent of each radian angle in the specified tensor. + /// The to take the sin of. + /// + public static ref readonly TensorSpan Tanh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IHyperbolicFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region TanPi + /// Computes the element-wise tangent of the value in the specified tensor that has been multiplied by Pi. + /// The to take the sin of. + public static Tensor TanPi(in ReadOnlyTensorSpan x) + where T : ITrigonometricFunctions + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise tangent of the value in the specified tensor that has been multiplied by Pi. + /// The to take the sin of. + /// + public static ref readonly TensorSpan TanPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : ITrigonometricFunctions + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region TrailingZeroCount + /// Computes the element-wise trailing zero count of numbers in the specified tensor. + /// The input . + public static Tensor TrailingZeroCount(in ReadOnlyTensorSpan x) + where T : IBinaryInteger + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise trailing zero count of numbers in the specified tensor. + /// The input . + /// + public static ref readonly TensorSpan TrailingZeroCount(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IBinaryInteger + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Truncate + /// Computes the element-wise truncation of numbers in the specified tensor. + /// The input . + public static Tensor Truncate(in ReadOnlyTensorSpan x) + where T : IFloatingPoint + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, destination); + return destination; + } + + /// Computes the element-wise truncation of numbers in the specified tensor. + /// The input . + /// + public static ref readonly TensorSpan Truncate(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) + where T : IFloatingPoint + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, destination); + return ref destination; + } + #endregion + + #region Xor + /// Computes the element-wise XOR of numbers in the specified tensors. + /// The left . + /// The right . + public static Tensor Xor(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(x, y, out Tensor destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// Computes the element-wise XOR of numbers in the specified tensors. + /// The left . + /// The right . + /// + public static ref readonly TensorSpan Xor(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(x, y, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; + } + + /// + /// Computes the element-wise Xor of the two input and returns a new with the result. + /// + /// The left . + /// The second value. + public static Tensor Xor(in ReadOnlyTensorSpan x, T y) + where T : IBitwiseOperators + { + Tensor destination = Tensor.CreateUninitialized(x.Lengths); + TensorOperation.Invoke, T, T>(x, y, destination); + return destination; + } + + /// + /// Computes the element-wise Xor of the two input and returns a new with the result. + /// + /// The left . + /// The second value. + /// + public static ref readonly TensorSpan Xor(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) + where T : IBitwiseOperators + { + TensorOperation.ValidateCompatibility(x, destination); + TensorOperation.Invoke, T, T>(x, y, destination); + return ref destination; } + #endregion + #endregion } } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs deleted file mode 100644 index 6b9dbec121b207..00000000000000 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs +++ /dev/null @@ -1,7183 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Buffers; -using static System.Runtime.InteropServices.JavaScript.JSType; -using System.Security.Cryptography; -using System.Runtime.Serialization; -using System.Diagnostics.CodeAnalysis; - -#pragma warning disable CS8601 // Possible null reference assignment. -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - -namespace System.Numerics.Tensors -{ - /// - /// Provides methods for tensor operations. - /// - [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - public static partial class Tensor - { - #region AsReadOnlySpan - /// - /// Extension method to more easily create a TensorSpan from an array. - /// - /// The type of the elements in the array - /// The with the data - /// The shape for the - /// - public static ReadOnlyTensorSpan AsReadOnlyTensorSpan(this T[]? array, params scoped ReadOnlySpan lengths) => new(array, 0, lengths, default); - #endregion - - #region AsTensorSpan - /// - /// Extension method to more easily create a TensorSpan from an array. - /// - /// The type of the elements in the array - /// The with the data - /// The shape for the - /// - public static TensorSpan AsTensorSpan(this T[]? array, params scoped ReadOnlySpan lengths) => new(array, 0, lengths, default); - #endregion - - #region Broadcast - /// - /// Broadcast the data from to the smallest broadcastable shape compatible with . Creates a new and allocates new memory. - /// - /// Input . - /// Other to make shapes broadcastable. - public static Tensor Broadcast(scoped in ReadOnlyTensorSpan source, scoped in ReadOnlyTensorSpan lengthsSource) - { - return Broadcast(source, lengthsSource.Lengths); - } - - /// - /// Broadcast the data from to the new shape . Creates a new and allocates new memory. - /// If the shape of the is not compatible with the new shape, an exception is thrown. - /// - /// Input . - /// of the desired new shape. - /// Thrown when the shapes are not broadcast compatible. - public static Tensor Broadcast(scoped in ReadOnlyTensorSpan source, scoped ReadOnlySpan lengths) - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(source.Lengths, lengths); - - ReadOnlyTensorSpan intermediate = LazyBroadcast(source, newSize); - Tensor output = Tensor.CreateUninitialized(intermediate.Lengths); - intermediate.FlattenTo(MemoryMarshal.CreateSpan(ref output._values[0], (int)output.FlattenedLength)); - return output; - } - #endregion - - #region BroadcastTo - /// - /// Broadcast the data from to . - /// - /// Input . - /// - public static void BroadcastTo(this Tensor source, in TensorSpan destination) - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(source.Lengths, destination.Lengths); - if (!destination.Lengths.SequenceEqual(newSize)) - ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible(); - - ReadOnlyTensorSpan intermediate = LazyBroadcast(source, newSize); - intermediate.FlattenTo(MemoryMarshal.CreateSpan(ref destination._reference, (int)destination.FlattenedLength)); - } - - /// - /// Broadcast the data from to . - /// - /// Input . - /// Other to make shapes broadcastable. - public static void BroadcastTo(in this TensorSpan source, in TensorSpan destination) - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(source.Lengths, destination.Lengths); - if (!destination.Lengths.SequenceEqual(newSize)) - ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible(); - - ReadOnlyTensorSpan intermediate = LazyBroadcast(source, newSize); - intermediate.FlattenTo(MemoryMarshal.CreateSpan(ref destination._reference, (int)destination.FlattenedLength)); - } - - /// - /// Broadcast the data from to . - /// - /// Input . - /// - public static void BroadcastTo(in this ReadOnlyTensorSpan source, in TensorSpan destination) - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(source.Lengths, destination.Lengths); - if (!destination.Lengths.SequenceEqual(newSize)) - ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible(); - - ReadOnlyTensorSpan intermediate = LazyBroadcast(source, newSize); - intermediate.FlattenTo(MemoryMarshal.CreateSpan(ref destination._reference, (int)destination.FlattenedLength)); - } - - // Lazy/non-copy broadcasting, internal only for now. - /// - /// Broadcast the data from to the new shape . Creates a new - /// but no memory is allocated. It manipulates the strides to achieve this affect. - /// If the shape of the is not compatible with the new shape, an exception is thrown. - /// - /// Input . - /// of the desired new shape. - /// Thrown when the shapes are not broadcast compatible. - internal static TensorSpan LazyBroadcast(in TensorSpan input, ReadOnlySpan shape) - { - if (input.Lengths.SequenceEqual(shape)) - return new TensorSpan(ref input._reference, shape, input.Strides, input._shape._memoryLength); - - if (!TensorHelpers.IsBroadcastableTo(input.Lengths, shape)) - ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible(); - - nint newSize = TensorSpanHelpers.CalculateTotalLength(shape); - - if (newSize == input.FlattenedLength) - return Reshape(input, shape); - - nint[] intermediateShape = TensorHelpers.GetIntermediateShape(input.Lengths, shape.Length); - nint[] strides = new nint[shape.Length]; - - nint stride = 1; - - for (int i = strides.Length - 1; i >= 0; i--) - { - if ((intermediateShape[i] == 1 && shape[i] != 1) || (intermediateShape[i] == 1 && shape[i] == 1)) - strides[i] = 0; - else - { - strides[i] = stride; - stride *= intermediateShape[i]; - } - } - - TensorSpan output = new TensorSpan(ref input._reference, shape, strides, input._shape._memoryLength); - - return output; - } - - // Lazy/non-copy broadcasting, internal only for now. - /// - /// Broadcast the data from to the new shape . Creates a new - /// but no memory is allocated. It manipulates the strides to achieve this affect. - /// If the shape of the is not compatible with the new shape, an exception is thrown. - /// - /// Input . - /// of the desired new shape. - /// Thrown when the shapes are not broadcast compatible. - internal static ReadOnlyTensorSpan LazyBroadcast(in ReadOnlyTensorSpan input, ReadOnlySpan shape) - { - if (input.Lengths.SequenceEqual(shape)) - return new TensorSpan(ref input._reference, shape, input.Strides, input._shape._memoryLength); - - if (!TensorHelpers.IsBroadcastableTo(input.Lengths, shape)) - ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible(); - - nint newSize = TensorSpanHelpers.CalculateTotalLength(shape); - - if (newSize == input.FlattenedLength) - return Reshape(input, shape); - - nint[] intermediateShape = TensorHelpers.GetIntermediateShape(input.Lengths, shape.Length); - nint[] strides = new nint[shape.Length]; - - nint stride = 1; - - for (int i = strides.Length - 1; i >= 0; i--) - { - if ((intermediateShape[i] == 1 && shape[i] != 1) || (intermediateShape[i] == 1 && shape[i] == 1)) - strides[i] = 0; - else - { - strides[i] = stride; - stride *= intermediateShape[i]; - } - } - - TensorSpan output = new TensorSpan(ref input._reference, shape, strides, input._shape._memoryLength); - - return output; - } - - // Lazy/non-copy broadcasting, internal only for now. - /// - /// Broadcast the data from to the new shape . Creates a new - /// but no memory is allocated. It manipulates the strides to achieve this affect. - /// If the shape of the is not compatible with the new shape, an exception is thrown. - /// - /// Input . - /// of the desired new shape. - /// Thrown when the shapes are not broadcast compatible. - internal static Tensor LazyBroadcast(Tensor input, ReadOnlySpan lengths) - { - if (input.Lengths.SequenceEqual(lengths)) - return new Tensor(input._values, lengths, input._memoryOffset, isPinned: false); - - if (!TensorHelpers.IsBroadcastableTo(input.Lengths, lengths)) - ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible(); - - nint newSize = TensorSpanHelpers.CalculateTotalLength(lengths); - - if (newSize == input.FlattenedLength) - return Reshape(input, lengths); - - nint[] intermediateShape = TensorHelpers.GetIntermediateShape(input.Lengths, lengths.Length); - nint[] strides = new nint[lengths.Length]; - - nint stride = 1; - - for (int i = strides.Length - 1; i >= 0; i--) - { - if ((intermediateShape[i] == 1 && lengths[i] != 1) || (intermediateShape[i] == 1 && lengths[i] == 1)) - strides[i] = 0; - else - { - strides[i] = stride; - stride *= intermediateShape[i]; - } - } - - Tensor output = new Tensor(input._values, lengths, strides, input._memoryOffset); - - return output; - } - #endregion - - #region Concatenate - /// - /// Join a sequence of tensors along an existing axis. - /// - /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). - public static Tensor Concatenate(params scoped ReadOnlySpan> tensors) - { - return ConcatenateOnDimension(0, tensors); - } - - /// - /// Join a sequence of tensors along an existing axis. - /// - /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). - /// The axis along which the tensors will be joined. If axis is -1, arrays are flattened before use. Default is 0. - public static Tensor ConcatenateOnDimension(int dimension, params scoped ReadOnlySpan> tensors) - { - if (tensors.Length < 2) - ThrowHelper.ThrowArgument_ConcatenateTooFewTensors(); - - if (dimension < -1 || dimension > tensors[0].Rank) - ThrowHelper.ThrowArgument_InvalidAxis(); - - // Calculate total space needed. - nint totalLength = 0; - for (int i = 0; i < tensors.Length; i++) - totalLength += TensorSpanHelpers.CalculateTotalLength(tensors[i].Lengths); - - nint sumOfAxis = 0; - // If axis != -1, make sure all dimensions except the one to concatenate on match. - if (dimension != -1) - { - sumOfAxis = tensors[0].Lengths[dimension]; - for (int i = 1; i < tensors.Length; i++) - { - if (tensors[0].Rank != tensors[i].Rank) - ThrowHelper.ThrowArgument_InvalidConcatenateShape(); - for (int j = 0; j < tensors[0].Rank; j++) - { - if (j != dimension) - { - if (tensors[0].Lengths[j] != tensors[i].Lengths[j]) - ThrowHelper.ThrowArgument_InvalidConcatenateShape(); - } - } - sumOfAxis += tensors[i].Lengths[dimension]; - } - } - - Tensor tensor; - if (dimension == -1) - { - tensor = Tensor.Create([totalLength]); - } - else - { - nint[] lengths = new nint[tensors[0].Rank]; - tensors[0].Lengths.CopyTo(lengths); - lengths[dimension] = sumOfAxis; - tensor = Tensor.Create(lengths); - } - - ConcatenateOnDimension(dimension, tensors, tensor); - return tensor; - } - - /// - /// Join a sequence of tensors along an existing axis. - /// - /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). - /// - public static ref readonly TensorSpan Concatenate(scoped ReadOnlySpan> tensors, in TensorSpan destination) - { - return ref ConcatenateOnDimension(0, tensors, destination); - } - - /// - /// Join a sequence of tensors along an existing axis. - /// - /// The tensors must have the same shape, except in the dimension corresponding to axis (the first, by default). - /// The axis along which the tensors will be joined. If axis is -1, arrays are flattened before use. Default is 0. - /// - - public static ref readonly TensorSpan ConcatenateOnDimension(int dimension, scoped ReadOnlySpan> tensors, in TensorSpan destination) - { - if (tensors.Length < 2) - ThrowHelper.ThrowArgument_ConcatenateTooFewTensors(); - - if (dimension < -1 || dimension > tensors[0].Rank) - ThrowHelper.ThrowArgument_InvalidAxis(); - - // Calculate total space needed. - nint totalLength = 0; - for (int i = 0; i < tensors.Length; i++) - totalLength += TensorSpanHelpers.CalculateTotalLength(tensors[i].Lengths); - - nint sumOfAxis = 0; - // If axis != -1, make sure all dimensions except the one to concatenate on match. - if (dimension != -1) - { - sumOfAxis = tensors[0].Lengths[dimension]; - for (int i = 1; i < tensors.Length; i++) - { - if (tensors[0].Rank != tensors[i].Rank) - ThrowHelper.ThrowArgument_InvalidConcatenateShape(); - for (int j = 0; j < tensors[0].Rank; j++) - { - if (j != dimension) - { - if (tensors[0].Lengths[j] != tensors[i].Lengths[j]) - ThrowHelper.ThrowArgument_InvalidConcatenateShape(); - } - } - sumOfAxis += tensors[i].Lengths[dimension]; - } - - // Make sure the destination tensor has the correct shape. - nint[] lengths = new nint[tensors[0].Rank]; - tensors[0].Lengths.CopyTo(lengths); - lengths[dimension] = sumOfAxis; - - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - } - Span dstSpan = MemoryMarshal.CreateSpan(ref destination._reference, (int)totalLength); - nint valuesCopied = 0; - - scoped Span curIndex; - nint[]? curIndexArray; - - if (tensors[0].Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(tensors[0].Rank); - curIndex = curIndexArray.AsSpan(0, tensors[0].Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[tensors[0].Rank]; - } - curIndex.Clear(); - - nint srcIndex; - nint copyLength; - - while (valuesCopied < totalLength) - { - for (int i = 0; i < tensors.Length; i++) - { - srcIndex = TensorSpanHelpers.ComputeLinearIndex(curIndex, tensors[i].Strides, tensors[i].Lengths); - copyLength = CalculateCopyLength(tensors[i].Lengths, dimension); - Span srcSpan = MemoryMarshal.CreateSpan(ref tensors[i]._values[srcIndex], (int)copyLength); - TensorSpanHelpers.Memmove(dstSpan, srcSpan, copyLength, valuesCopied); - valuesCopied += copyLength; - } - TensorSpanHelpers.AdjustIndexes(dimension - 1, 1, curIndex, tensors[0].Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - private static nint CalculateCopyLength(ReadOnlySpan lengths, int startingAxis) - { - // When starting axis is -1 we want all the data at once same as if starting axis is 0 - if (startingAxis == -1) - startingAxis = 0; - nint length = 1; - for (int i = startingAxis; i < lengths.Length; i++) - { - length *= lengths[i]; - } - return length; - } - #endregion - - #region Equals - /// - /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size - /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// A where the value is true if the elements are equal and false if they are not. - public static Tensor Equals(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IEqualityOperators - { - Tensor result; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - result = Tensor.Create(x.Lengths, false); - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - result = Tensor.Create(newSize, false); - } - - Equals(x, y, result); - return result; - } - - /// - /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size - /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// - /// A where the value is true if the elements are equal and false if they are not. - public static ref readonly TensorSpan Equals(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IEqualityOperators - { - scoped ReadOnlyTensorSpan left; - scoped ReadOnlyTensorSpan right; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = x; - right = y; - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, newSize)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = LazyBroadcast(x, newSize); - right = LazyBroadcast(y, newSize); - } - - scoped Span curIndex; - nint[]? curIndexArray; - - if (right.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray.AsSpan(0, right.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[right.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < left.FlattenedLength; i++) - { - destination[curIndex] = left[curIndex] == right[curIndex]; - TensorSpanHelpers.AdjustIndexes(right.Rank - 1, 1, curIndex, right.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size - /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> - /// - /// First to compare. - /// Second value to compare. - /// A where the value is true if the elements are equal and false if they are not. - public static Tensor Equals(in ReadOnlyTensorSpan x, T y) - where T : IEqualityOperators - { - Tensor result = Tensor.Create(x.Lengths, false); - Equals(x, y, result); - return result; - } - - /// - /// Compares the elements of two for equality. If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size - /// before they are compared. It returns a where the value is true if the elements are equal and false if they are not."/> - /// - /// First to compare. - /// Second value to compare. - /// - /// A where the value is true if the elements are equal and false if they are not. - public static ref readonly TensorSpan Equals(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IEqualityOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - destination[curIndex] = x[curIndex] == y; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - #endregion - - #region EqualsAll - /// - /// Compares the elements of two to see if all elements of are equal to . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are eqaul to . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are equal to . - public static bool EqualsAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IEqualityOperators - { - - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedLeft.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Rank); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] != broadcastedRight[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are equal to . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are eqaul to . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are equal to . - public static bool EqualsAll(in ReadOnlyTensorSpan x, T y) - where T : IEqualityOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] != y) - return false; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - #endregion - - #region EqualsAny - /// - /// Compares the elements of two to see if any elements of are equal to . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are equal to . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if any elements in are equal to . - public static bool EqualsAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IEqualityOperators - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedRight.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] == broadcastedRight[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are equal to . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are equal to . - /// - /// First to compare. - /// Value to compare against. - /// where the value is true if any elements in are equal to . - public static bool EqualsAny(in ReadOnlyTensorSpan x, T y) - where T : IEqualityOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] == y) - return true; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - #endregion - - #region FilteredUpdate - /// - /// Updates the tensor with the where the is true. - /// - /// Input . - /// Input filter where if the index is true then it will update the . - /// Value to update in the . - public static ref readonly TensorSpan FilteredUpdate(in this TensorSpan tensor, scoped in ReadOnlyTensorSpan filter, T value) - { - if (filter.Lengths.Length != tensor.Lengths.Length) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(filter)); - - Span srcSpan = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape._memoryLength); - Span filterSpan = MemoryMarshal.CreateSpan(ref filter._reference, (int)tensor._shape._memoryLength); - - for (int i = 0; i < filterSpan.Length; i++) - { - if (filterSpan[i]) - { - srcSpan[i] = value; - } - } - - return ref tensor; - } - - /// - /// Updates the tensor with the where the is true. - /// If dimensions are not the same an exception is thrown. - /// - /// Input . - /// Input filter where if the index is true then it will update the . - /// Values to update in the . - public static ref readonly TensorSpan FilteredUpdate(in this TensorSpan tensor, scoped in ReadOnlyTensorSpan filter, scoped in ReadOnlyTensorSpan values) - { - if (filter.Lengths.Length != tensor.Lengths.Length) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(filter)); - if (values.Rank != 1) - ThrowHelper.ThrowArgument_1DTensorRequired(nameof(values)); - - Span dstSpan = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape._memoryLength); - Span filterSpan = MemoryMarshal.CreateSpan(ref filter._reference, (int)tensor._shape._memoryLength); - Span valuesSpan = MemoryMarshal.CreateSpan(ref values._reference, (int)values._shape._memoryLength); - - int index = 0; - for (int i = 0; i < filterSpan.Length; i++) - { - if (filterSpan[i]) - { - dstSpan[i] = valuesSpan[index++]; - } - } - - return ref tensor; - } - #endregion - - #region GreaterThan - /// - /// Compares the elements of two to see which elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// A where the value is true if the elements in are greater than and - /// false if they are not. - public static Tensor GreaterThan(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - result = Tensor.Create(x.Lengths, false); - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - result = Tensor.Create(newSize, false); - } - - GreaterThan(x, y, result); - return result; - } - - /// - /// Compares the elements of two to see which elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// - /// A where the value is true if the elements in are greater than and - /// false if they are not. - public static ref readonly TensorSpan GreaterThan(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - scoped ReadOnlyTensorSpan left; - scoped ReadOnlyTensorSpan right; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = x; - right = y; - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, newSize)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = LazyBroadcast(x, newSize); - right = LazyBroadcast(y, newSize); - } - - scoped Span curIndex; - nint[]? curIndexArray; - - if (right.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray.AsSpan(0, right.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[right.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < left.FlattenedLength; i++) - { - destination[curIndex] = left[curIndex] > right[curIndex]; - TensorSpanHelpers.AdjustIndexes(right.Rank - 1, 1, curIndex, right.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares the elements of a to see which elements are greater than . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static Tensor GreaterThan(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(x.Lengths, false); - GreaterThan(x, y, result); - return result; - } - - /// - /// Compares the elements of a to see which elements are greater than . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static ref readonly TensorSpan GreaterThan(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - destination[curIndex] = x[curIndex] > y; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares to see which elements are greater than . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static Tensor GreaterThan(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(y.Lengths, false); - GreaterThan(x, y, result); - return result; - } - - /// - /// Compares to see which elements are greater than . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static ref readonly TensorSpan GreaterThan(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, y.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - destination[curIndex] = x > y[curIndex]; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - #endregion - - #region GreaterThanOrEqual - /// - /// Compares the elements of two to see which elements of are greater than or equal to . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// A where the value is true if the elements in are greater than and - /// false if they are not. - public static Tensor GreaterThanOrEqual(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - result = Tensor.Create(x.Lengths, false); - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - result = Tensor.Create(newSize, false); - } - - GreaterThanOrEqual(x, y, result); - return result; - } - - /// - /// Compares the elements of two to see which elements of are greater than or equal to . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// - /// A where the value is true if the elements in are greater than and - /// false if they are not. - public static ref readonly TensorSpan GreaterThanOrEqual(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - scoped ReadOnlyTensorSpan left; - scoped ReadOnlyTensorSpan right; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = x; - right = y; - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, newSize)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = LazyBroadcast(x, newSize); - right = LazyBroadcast(y, newSize); - } - - scoped Span curIndex; - nint[]? curIndexArray; - - if (right.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray.AsSpan(0, right.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[right.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < left.FlattenedLength; i++) - { - destination[curIndex] = left[curIndex] >= right[curIndex]; - TensorSpanHelpers.AdjustIndexes(right.Rank - 1, 1, curIndex, right.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares the elements of a to see which elements are greater than or equal to . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static Tensor GreaterThanOrEqual(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(x.Lengths, false); - GreaterThanOrEqual(x, y, result); - return result; - } - - /// - /// Compares the elements of a to see which elements are greater than or equal to . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static ref readonly TensorSpan GreaterThanOrEqual(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - destination[curIndex] = x[curIndex] >= y; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares to see which elements are greater than or equal to . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static Tensor GreaterThanOrEqual(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(y.Lengths, false); - GreaterThanOrEqual(x, y, result); - return result; - } - - /// - /// Compares to see which elements are greater than or equal to . - /// It returns a where the value is true if the elements in are greater than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are greater than - /// and false if they are not. - public static ref readonly TensorSpan GreaterThanOrEqual(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, y.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - destination[curIndex] = x >= y[curIndex]; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - #endregion - - #region GreaterThanAny - /// - /// Compares the elements of two to see if any elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if any elements in are greater than . - public static bool GreaterThanAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedRight.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] > broadcastedRight[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are greater than . - /// - /// First to compare. - /// Value to compare against. - /// where the value is true if any elements in are greater than . - public static bool GreaterThanAny(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] > y) - return true; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are greater than . - /// - /// First to compare. - /// Value to compare against. - /// where the value is true if any elements in are greater than . - public static bool GreaterThanAny(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - if (x > y[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - #endregion - - #region GreaterThanOrEqualAny - /// - /// Compares the elements of two to see if any elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if any elements in are greater than . - public static bool GreaterThanOrEqualAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedRight.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] >= broadcastedRight[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are greater than . - /// - /// First to compare. - /// Value to compare against. - /// where the value is true if any elements in are greater than . - public static bool GreaterThanOrEqualAny(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] >= y) - return true; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are greater than . - /// - /// First to compare. - /// Value to compare against. - /// where the value is true if any elements in are greater than . - public static bool GreaterThanOrEqualAny(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - if (x >= y[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - #endregion - - #region GreaterThanAll - /// - /// Compares the elements of two to see if all elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are greater than . - public static bool GreaterThanAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedLeft.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Rank); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] <= broadcastedRight[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are greater than . - public static bool GreaterThanAll(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] <= y) - return false; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are greater than . - public static bool GreaterThanAll(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - if (x <= y[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - #endregion - - #region GreaterThanOrEqualAll - /// - /// Compares the elements of two to see if all elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are greater than . - public static bool GreaterThanOrEqualAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedLeft.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Rank); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] < broadcastedRight[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are greater than . - public static bool GreaterThanOrEqualAll(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] < y) - return false; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are greater than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are greater than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are greater than . - public static bool GreaterThanOrEqualAll(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - if (x < y[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - #endregion - - #region LessThan - /// - /// Compares the elements of two to see which elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// A where the value is true if the elements in are less than and - /// false if they are not. - public static Tensor LessThan(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - result = Tensor.Create(x.Lengths, false); - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - result = Tensor.Create(newSize, false); - } - - LessThan(x, y, result); - return result; - } - - /// - /// Compares the elements of two to see which elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// - /// A where the value is true if the elements in are less than and - /// false if they are not. - public static ref readonly TensorSpan LessThan(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - scoped ReadOnlyTensorSpan left; - scoped ReadOnlyTensorSpan right; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = x; - right = y; - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, newSize)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = LazyBroadcast(x, newSize); - right = LazyBroadcast(y, newSize); - } - - scoped Span curIndex; - nint[]? curIndexArray; - - if (right.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray.AsSpan(0, right.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[right.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < left.FlattenedLength; i++) - { - destination[curIndex] = left[curIndex] < right[curIndex]; - TensorSpanHelpers.AdjustIndexes(right.Rank - 1, 1, curIndex, right.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are less than - /// and false if they are not. - public static Tensor LessThan(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(x.Lengths, false); - LessThan(x, y, result); - return result; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are less than - /// and false if they are not. - public static ref readonly TensorSpan LessThan(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - destination[curIndex] = x[curIndex] < y; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are less than - /// and false if they are not. - public static Tensor LessThan(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(y.Lengths, false); - LessThan(x, y, result); - return result; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are less than - /// and false if they are not. - public static ref readonly TensorSpan LessThan(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, y.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - destination[curIndex] = x < y[curIndex]; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - #endregion - - #region LessThanOrEqual - /// - /// Compares the elements of two to see which elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// A where the value is true if the elements in are less than and - /// false if they are not. - public static Tensor LessThanOrEqual(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - result = Tensor.Create(x.Lengths, false); - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - result = Tensor.Create(newSize, false); - } - - LessThanOrEqual(x, y, result); - return result; - } - - /// - /// Compares the elements of two to see which elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// First to compare. - /// Second to compare. - /// - /// A where the value is true if the elements in are less than and - /// false if they are not. - public static ref readonly TensorSpan LessThanOrEqual(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - scoped ReadOnlyTensorSpan left; - scoped ReadOnlyTensorSpan right; - if (TensorHelpers.AreLengthsTheSame(x, y)) - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = x; - right = y; - } - else - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, newSize)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - left = LazyBroadcast(x, newSize); - right = LazyBroadcast(y, newSize); - } - - scoped Span curIndex; - nint[]? curIndexArray; - - if (right.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(right.Rank); - curIndex = curIndexArray.AsSpan(0, right.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[right.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < left.FlattenedLength; i++) - { - destination[curIndex] = left[curIndex] <= right[curIndex]; - TensorSpanHelpers.AdjustIndexes(right.Rank - 1, 1, curIndex, right.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are less than - /// and false if they are not. - public static Tensor LessThanOrEqual(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(x.Lengths, false); - LessThanOrEqual(x, y, result); - return result; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are less than - /// and false if they are not. - public static ref readonly TensorSpan LessThanOrEqual(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, x.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - destination[curIndex] = x[curIndex] <= y; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// where the value is true if the elements in are less than - /// and false if they are not. - public static Tensor LessThanOrEqual(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - Tensor result = Tensor.Create(y.Lengths, false); - LessThanOrEqual(x, y, result); - return result; - } - - /// - /// Compares the elements of a to see which elements are less than . - /// It returns a where the value is true if the elements in are less than - /// and false if they are not."/> - /// - /// to compare. - /// to compare against . - /// - /// where the value is true if the elements in are less than - /// and false if they are not. - public static ref readonly TensorSpan LessThanOrEqual(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IComparisonOperators - { - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, y.Lengths)) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(destination)); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - destination[curIndex] = x <= y[curIndex]; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return ref destination; - } - #endregion - - #region LessThanAny - /// - /// Compares the elements of two to see if any elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are less than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if any elements in are less than . - public static bool LessThanAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedRight.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] < broadcastedRight[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are less than . - /// - /// First to compare. - /// Second value to compare against. - /// where the value is true if any elements in are less than . - public static bool LessThanAny(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] < y) - return true; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are less than . - /// - /// First value to compare. - /// Second value to compare against. - /// where the value is true if any elements in are less than . - public static bool LessThanAny(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - if (x < y[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - #endregion - - #region LessThanOrEqualAny - /// - /// Compares the elements of two to see if any elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are less than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if any elements in are less than . - public static bool LessThanOrEqualAny(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedRight.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] <= broadcastedRight[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are less than . - /// - /// First to compare. - /// Second value to compare against. - /// where the value is true if any elements in are less than . - public static bool LessThanOrEqualAny(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] <= y) - return true; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - - /// - /// Compares the elements of two to see if any elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if any elements in are less than . - /// - /// First value to compare. - /// Second value to compare against. - /// where the value is true if any elements in are less than . - public static bool LessThanOrEqualAny(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i <= y.FlattenedLength; i++) - { - if (x <= y[curIndex]) - return true; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return false; - } - #endregion - - #region LessThanAll - /// - /// Compares the elements of two to see if all elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are less than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are less than . - public static bool LessThanAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedRight.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] >= broadcastedRight[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are less than . - /// - /// First to compare. - /// Second value to compare against. - /// where the value is true if all elements in are less than . - public static bool LessThanAll(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] >= y) - return false; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are less than . - /// - /// First value to compare. - /// Second value to compare against. - /// where the value is true if all elements in are less than . - public static bool LessThanAll(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - if (x >= y[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - #endregion - - #region LessThanOrEqualAll - /// - /// Compares the elements of two to see if all elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are less than . - /// - /// First to compare. - /// Second to compare against. - /// where the value is true if all elements in are less than . - public static bool LessThanOrEqualAll(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(x.Lengths, y.Lengths); - ReadOnlyTensorSpan broadcastedLeft = LazyBroadcast(x, newSize); - ReadOnlyTensorSpan broadcastedRight = LazyBroadcast(y, newSize); - - scoped Span curIndex; - nint[]? curIndexArray; - - if (broadcastedRight.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(broadcastedRight.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, broadcastedRight.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[broadcastedRight.Lengths.Length]; - } - curIndex.Clear(); - - for (int i = 0; i < broadcastedLeft.FlattenedLength; i++) - { - if (broadcastedLeft[curIndex] > broadcastedRight[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(broadcastedRight.Rank - 1, 1, curIndex, broadcastedRight.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are less than . - /// - /// First to compare. - /// Second value to compare against. - /// where the value is true if all elements in are less than . - public static bool LessThanOrEqualAll(in ReadOnlyTensorSpan x, T y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (x.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(x.Rank); - curIndex = curIndexArray.AsSpan(0, x.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[x.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < x.FlattenedLength; i++) - { - if (x[curIndex] > y) - return false; - TensorSpanHelpers.AdjustIndexes(x.Rank - 1, 1, curIndex, x.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - - /// - /// Compares the elements of two to see if all elements of are less than . - /// If the shapes are not the same, the tensors are broadcasted to the smallest broadcastable size before they are compared. - /// It returns a where the value is true if all elements in are less than . - /// - /// First value to compare. - /// Second value to compare against. - /// where the value is true if all elements in are less than . - public static bool LessThanOrEqualAll(T x, in ReadOnlyTensorSpan y) - where T : IComparisonOperators - { - scoped Span curIndex; - nint[]? curIndexArray; - - if (y.Rank > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(y.Rank); - curIndex = curIndexArray.AsSpan(0, y.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[y.Rank]; - } - curIndex.Clear(); - - for (int i = 0; i < y.FlattenedLength; i++) - { - if (x > y[curIndex]) - return false; - TensorSpanHelpers.AdjustIndexes(y.Rank - 1, 1, curIndex, y.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - - return true; - } - #endregion - - #region Permute - - /// - /// Swaps the dimensions of the tensor according to the parameter. - /// If is a 1D tensor, it will return . Otherwise it creates a new - /// with the new axis ordering by allocating new memory. - /// - /// Input - /// with the new axis ordering. - public static Tensor PermuteDimensions(this Tensor tensor, params ReadOnlySpan dimensions) - { - if (tensor.Rank == 1) - { - return tensor; - } - else - { - T[] values = tensor.IsPinned ? GC.AllocateArray((int)tensor._flattenedLength) : (new T[tensor._flattenedLength]); - nint[] lengths = new nint[tensor.Rank]; - Tensor outTensor; - TensorSpan ospan; - TensorSpan ispan; - ReadOnlySpan permutation; - - if (dimensions.IsEmpty) - { - int[] tempPermutation = new int[tensor.Rank]; - for (int i = 0; i < tensor.Rank; i++) - { - lengths[i] = tensor._lengths[tensor.Rank - 1 - i]; - tempPermutation[i] = tensor.Rank - 1 - i; - } - - permutation = tempPermutation; - } - else - { - if (dimensions.Length != tensor.Lengths.Length) - ThrowHelper.ThrowArgument_PermuteAxisOrder(); - for (int i = 0; i < lengths.Length; i++) - lengths[i] = tensor.Lengths[dimensions[i]]; - permutation = dimensions.ToArray(); - } - outTensor = new Tensor(values, lengths, Array.Empty(), tensor._memoryOffset, tensor._isPinned); - - ospan = outTensor.AsTensorSpan(); - ispan = tensor.AsTensorSpan(); - - scoped Span indexes; - nint[]? indicesArray; - scoped Span permutedIndices; - nint[]? permutedIndicesArray; - if (outTensor.Rank > 6) - { - indicesArray = ArrayPool.Shared.Rent(outTensor.Rank); - indexes = indicesArray.AsSpan(0, outTensor.Rank); - indexes.Clear(); - - permutedIndicesArray = ArrayPool.Shared.Rent(outTensor.Rank); - permutedIndices = permutedIndicesArray.AsSpan(0, outTensor.Rank); - permutedIndices.Clear(); - } - else - { - indicesArray = null; - indexes = stackalloc nint[outTensor.Rank]; - permutedIndicesArray = null; - permutedIndices = stackalloc nint[outTensor.Rank]; - } - - for (int i = 0; i < tensor._flattenedLength; i++) - { - TensorHelpers.PermuteIndices(indexes, permutedIndices, permutation); - ospan[permutedIndices] = ispan[indexes]; - TensorSpanHelpers.AdjustIndexes(outTensor.Rank - 1, 1, indexes, tensor._lengths); - } - - if (indicesArray != null && permutedIndicesArray != null) - { - ArrayPool.Shared.Return(indicesArray); - ArrayPool.Shared.Return(permutedIndicesArray); - } - - return outTensor; - } - } - #endregion - - #region Reshape - /// - /// Reshapes the tensor to the specified . If one of the lengths is -1, it will be calculated automatically. - /// Does not change the length of the underlying memory nor does it allocate new memory. If the new shape is not compatible with the old shape, - /// an exception is thrown. - /// - /// you want to reshape. - /// with the new dimensions. - public static Tensor Reshape(this Tensor tensor, params ReadOnlySpan lengths) - { - if (tensor.Lengths.SequenceEqual(lengths)) - return tensor; - - if (!TensorHelpers.IsContiguousAndDense(tensor) && !tensor.Strides.Contains(0)) - { - ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); - } - - nint[] arrLengths = lengths.ToArray(); - // Calculate wildcard info. - if (lengths.Contains(-1)) - { - if (lengths.Count(-1) > 1) - ThrowHelper.ThrowArgument_OnlyOneWildcard(); - nint tempTotal = tensor._flattenedLength; - for (int i = 0; i < lengths.Length; i++) - { - if (lengths[i] != -1) - { - tempTotal /= lengths[i]; - } - } - arrLengths[lengths.IndexOf(-1)] = tempTotal; - - } - - nint tempLinear = TensorSpanHelpers.CalculateTotalLength(arrLengths); - if (tempLinear != tensor.FlattenedLength) - ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); - - nint[] strides; - - // If we contain a 0 stride we can only add dimensions of length 1. - if (tensor.Strides.Contains(0)) - { - List origStrides = new List(tensor.Strides.ToArray()); - int lengthOffset = 0; - for (int i = 0; i < arrLengths.Length; i++) - { - if (lengthOffset < tensor.Rank && arrLengths[i] == tensor.Lengths[lengthOffset]) - lengthOffset++; - else if (arrLengths[i] == 1) - { - if (lengthOffset == tensor.Rank) - origStrides.Add(tensor.Strides[lengthOffset - 1]); - else - origStrides.Insert(i, tensor.Strides[i] * tensor.Lengths[i]); - } - else - ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); - } - strides = origStrides.ToArray(); - } - else - strides = TensorSpanHelpers.CalculateStrides(arrLengths); - - return new Tensor(tensor._values, arrLengths, strides, tensor._memoryOffset); - } - - /// - /// Reshapes the tensor to the specified . If one of the lengths is -1, it will be calculated automatically. - /// Does not change the length of the underlying memory nor does it allocate new memory. If the new shape is not compatible with the old shape, - /// an exception is thrown. - /// - /// you want to reshape. - /// with the new dimensions. - public static TensorSpan Reshape(in this TensorSpan tensor, params scoped ReadOnlySpan lengths) - { - if (tensor.Lengths.SequenceEqual(lengths)) - return tensor; - - if (!TensorHelpers.IsContiguousAndDense(tensor) && !tensor.Strides.Contains(0)) - { - ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); - } - - nint[] arrLengths = lengths.ToArray(); - // Calculate wildcard info. - if (lengths.Contains(-1)) - { - if (lengths.Count(-1) > 1) - ThrowHelper.ThrowArgument_OnlyOneWildcard(); - nint tempTotal = tensor.FlattenedLength; - for (int i = 0; i < lengths.Length; i++) - { - if (lengths[i] != -1) - { - tempTotal /= lengths[i]; - } - } - arrLengths[lengths.IndexOf(-1)] = tempTotal; - - } - - nint tempLinear = TensorSpanHelpers.CalculateTotalLength(arrLengths); - if (tempLinear != tensor.FlattenedLength) - ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); - - nint[] strides; - - // If we contain a 0 stride we can only add dimensions of length 1. - if (tensor.Strides.Contains(0)) - { - List origStrides = new List(tensor.Strides.ToArray()); - int lengthOffset = 0; - for (int i = 0; i < arrLengths.Length; i++) - { - if (lengthOffset < tensor.Rank && arrLengths[i] == tensor.Lengths[lengthOffset]) - { - lengthOffset++; - } - else if (arrLengths[i] == 1) - { - if (lengthOffset == tensor.Rank) - origStrides.Add(tensor.Strides[lengthOffset - 1]); - else - origStrides.Insert(i, tensor.Strides[i] * tensor.Lengths[i]); - } - else - ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); - } - strides = origStrides.ToArray(); - } - else - strides = TensorSpanHelpers.CalculateStrides(arrLengths); - - TensorSpan output = new TensorSpan(ref tensor._reference, arrLengths, strides, tensor._shape._memoryLength); - return output; - } - - /// - /// Reshapes the tensor to the specified . If one of the lengths is -1, it will be calculated automatically. - /// Does not change the length of the underlying memory nor does it allocate new memory. If the new shape is not compatible with the old shape, - /// an exception is thrown. - /// - /// you want to reshape. - /// with the new dimensions. - public static ReadOnlyTensorSpan Reshape(in this ReadOnlyTensorSpan tensor, params scoped ReadOnlySpan lengths) - { - if (tensor.Lengths.SequenceEqual(lengths)) - return tensor; - - if (!TensorHelpers.IsContiguousAndDense(tensor) && !tensor.Strides.Contains(0)) - { - ThrowHelper.ThrowArgument_CannotReshapeNonContiguousOrDense(); - } - - nint[] arrLengths = lengths.ToArray(); - // Calculate wildcard info. - if (lengths.Contains(-1)) - { - if (lengths.Count(-1) > 1) - ThrowHelper.ThrowArgument_OnlyOneWildcard(); - nint tempTotal = tensor.FlattenedLength; - for (int i = 0; i < lengths.Length; i++) - { - if (lengths[i] != -1) - { - tempTotal /= lengths[i]; - } - } - arrLengths[lengths.IndexOf(-1)] = tempTotal; - - } - - nint tempLinear = TensorSpanHelpers.CalculateTotalLength(arrLengths); - if (tempLinear != tensor.FlattenedLength) - ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); - - nint[] strides; - - // If we contain a 0 stride we can only add dimensions of length 1. - if (tensor.Strides.Contains(0)) - { - List origStrides = new List(tensor.Strides.ToArray()); - int lengthOffset = 0; - for (int i = 0; i < arrLengths.Length; i++) - { - if (lengthOffset < tensor.Rank && arrLengths[i] == tensor.Lengths[lengthOffset]) - lengthOffset++; - else if (arrLengths[i] == 1) - { - if (lengthOffset == tensor.Rank) - origStrides.Add(tensor.Strides[lengthOffset - 1]); - else - origStrides.Insert(i, tensor.Strides[i] * tensor.Lengths[i]); - } - else - ThrowHelper.ThrowArgument_InvalidReshapeDimensions(); - } - strides = origStrides.ToArray(); - } - else - strides = TensorSpanHelpers.CalculateStrides(arrLengths); - - ReadOnlyTensorSpan output = new ReadOnlyTensorSpan(ref tensor._reference, arrLengths, strides, tensor._shape._memoryLength); - return output; - } - #endregion - - #region Resize - /// - /// Creates a new , allocates new memory, and copies the data from . If the final shape is smaller all data after - /// that point is ignored. - /// - /// Input . - /// of the desired new shape. - public static Tensor Resize(Tensor tensor, ReadOnlySpan lengths) - { - nint newSize = TensorSpanHelpers.CalculateTotalLength(lengths); - T[] values = tensor.IsPinned ? GC.AllocateArray((int)newSize) : (new T[newSize]); - Tensor output = new Tensor(values, lengths, tensor._memoryOffset, isPinned: false); - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensor.AsTensorSpan()._reference, (int)tensor._values.Length); - Span ospan = MemoryMarshal.CreateSpan(ref output.AsTensorSpan()._reference, (int)output.FlattenedLength); - if (newSize > tensor._values.Length) - TensorSpanHelpers.Memmove(ospan, span, tensor._values.Length); - else - TensorSpanHelpers.Memmove(ospan, span, newSize); - - return output; - } - - /// - /// 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. - /// - /// Input . - /// Destination with the desired new shape. - public static void ResizeTo(scoped in Tensor tensor, in TensorSpan destination) - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensor._values[0], tensor._values.Length); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - if (destination._shape._memoryLength > tensor._values.Length) - TensorSpanHelpers.Memmove(ospan, span, tensor._values.Length); - else - TensorSpanHelpers.Memmove(ospan, span, destination._shape._memoryLength); - } - - /// - /// 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. - /// - /// Input . - /// Destination with the desired new shape. - public static void ResizeTo(scoped in TensorSpan tensor, in TensorSpan destination) - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - if (destination._shape._memoryLength > tensor._shape._memoryLength) - TensorSpanHelpers.Memmove(ospan, span, tensor._shape._memoryLength); - else - TensorSpanHelpers.Memmove(ospan, span, destination._shape._memoryLength); - } - - /// - /// 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. - /// - /// Input . - /// Destination with the desired new shape. - public static void ResizeTo(scoped in ReadOnlyTensorSpan tensor, in TensorSpan destination) - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - if (destination._shape._memoryLength > tensor._shape._memoryLength) - TensorSpanHelpers.Memmove(ospan, span, tensor._shape._memoryLength); - else - TensorSpanHelpers.Memmove(ospan, span, destination._shape._memoryLength); - } - #endregion - - #region Reverse - /// - /// Reverse the order of elements in the . The shape of the tensor is preserved, but the elements are reordered. - /// - /// Input . - public static Tensor Reverse(in ReadOnlyTensorSpan tensor) - { - Tensor output = Tensor.Create(tensor.Lengths); - ReverseDimension(tensor, output, -1); - - return output; - } - - /// - /// Reverse the order of elements in the along the given dimension. The shape of the tensor is preserved, but the elements are reordered. - /// defaults to -1 when not provided, which reverses the entire tensor. - /// - /// Input . - /// dimension along which to reverse over. -1 will reverse over all of the dimensions of the left tensor. - public static Tensor ReverseDimension(in ReadOnlyTensorSpan tensor, int dimension) - { - Tensor output = Tensor.Create(tensor.Lengths); - ReverseDimension(tensor, output, dimension); - - return output; - } - - /// - /// Reverse the order of elements in the . The shape of the tensor is preserved, but the elements are reordered. - /// - /// Input . - /// - public static ref readonly TensorSpan Reverse(scoped in ReadOnlyTensorSpan tensor, in TensorSpan destination) - { - return ref ReverseDimension(tensor, destination, -1); - } - - /// - /// Reverse the order of elements in the along the given axis. The shape of the tensor is preserved, but the elements are reordered. - /// defaults to -1 when not provided, which reverses the entire span. - /// - /// Input . - /// - /// dimension along which to reverse over. -1 will reverse over all of the dimensions of the left tensor. - public static ref readonly TensorSpan ReverseDimension(scoped in ReadOnlyTensorSpan tensor, in TensorSpan destination, int dimension) - { - if (dimension == -1) - { - nint index = tensor._shape._memoryLength - 1; - Span inputSpan = MemoryMarshal.CreateSpan(ref tensor._reference, (int)tensor._shape._memoryLength); - Span outputSpan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - for (int i = 0; i <= tensor._shape._memoryLength / 2; i++) - { - outputSpan[i] = inputSpan[(int)index]; - outputSpan[(int)index--] = inputSpan[i]; - } - } - else - { - nint copyLength = 1; - for (nint i = dimension; i < tensor.Lengths.Length; i++) - { - copyLength *= tensor.Lengths[(int)i]; - } - copyLength /= tensor.Lengths[(int)dimension]; - - scoped Span oIndices; - nint[]? oIndicesArray; - scoped Span iIndices; - nint[]? iIndicesArray; - if (tensor.Rank > 6) - { - oIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - oIndices = oIndicesArray.AsSpan(0, tensor.Rank); - oIndices.Clear(); - - iIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - iIndices = iIndicesArray.AsSpan(0, tensor.Rank); - iIndices.Clear(); - } - else - { - oIndicesArray = null; - oIndices = stackalloc nint[tensor.Rank]; - iIndicesArray = null; - iIndices = stackalloc nint[tensor.Rank]; - } - - iIndices[(int)dimension] = tensor.Lengths[(int)dimension] - 1; - nint copiedValues = 0; - ReadOnlyTensorSpan islice = tensor.Slice(tensor.Lengths); - - while (copiedValues < tensor.FlattenedLength) - { - TensorSpanHelpers.Memmove(ref Unsafe.Add(ref destination._reference, TensorSpanHelpers.ComputeLinearIndex(oIndices, tensor.Strides, tensor.Lengths)), ref Unsafe.Add(ref islice._reference, TensorSpanHelpers.ComputeLinearIndex(iIndices, islice.Strides, islice.Lengths)), copyLength); - TensorSpanHelpers.AdjustIndexes((int)dimension, 1, oIndices, tensor.Lengths); - TensorSpanHelpers.AdjustIndexesDown((int)dimension, 1, iIndices, tensor.Lengths); - copiedValues += copyLength; - } - - if (oIndicesArray != null && iIndicesArray != null) - { - ArrayPool.Shared.Return(oIndicesArray); - ArrayPool.Shared.Return(iIndicesArray); - } - } - - return ref destination; - } - #endregion - - #region SequenceEqual - /// - /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). - /// - public static bool SequenceEqual(this scoped in TensorSpan tensor, scoped in ReadOnlyTensorSpan other) - where T : IEquatable? - { - return tensor.FlattenedLength == other.FlattenedLength - && tensor._shape._memoryLength == other._shape._memoryLength - && tensor.Lengths.SequenceEqual(other.Lengths) - && MemoryMarshal.CreateReadOnlySpan(in tensor.GetPinnableReference(), (int)tensor._shape._memoryLength).SequenceEqual(MemoryMarshal.CreateReadOnlySpan(in other.GetPinnableReference(), (int)other._shape._memoryLength)); - } - - /// - /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). - /// - public static bool SequenceEqual(this scoped in ReadOnlyTensorSpan tensor, scoped in ReadOnlyTensorSpan other) - where T : IEquatable? - { - return tensor.FlattenedLength == other.FlattenedLength - && tensor._shape._memoryLength == other._shape._memoryLength - && tensor.Lengths.SequenceEqual(other.Lengths) - && MemoryMarshal.CreateReadOnlySpan(in tensor.GetPinnableReference(), (int)tensor._shape._memoryLength).SequenceEqual(MemoryMarshal.CreateReadOnlySpan(in other.GetPinnableReference(), (int)other._shape._memoryLength)); - } - #endregion - - #region SetSlice - /// - /// Sets a slice of the given with the provided for the given - /// - /// Input . - /// The values you want to set in the . - /// The ranges you want to set. - public static Tensor SetSlice(this Tensor tensor, in ReadOnlyTensorSpan values, params ReadOnlySpan ranges) - { - SetSlice((TensorSpan)tensor, values, ranges); - - return tensor; - } - - /// - /// Sets a slice of the given with the provided for the given - /// - /// Input . - /// The values you want to set in the . - /// The ranges you want to set. - public static ref readonly TensorSpan SetSlice(this in TensorSpan tensor, scoped in ReadOnlyTensorSpan values, params scoped ReadOnlySpan ranges) - { - TensorSpan srcSpan; - if (ranges == ReadOnlySpan.Empty) - { - if (!TensorHelpers.IsBroadcastableTo(values.Lengths, tensor.Lengths)) - ThrowHelper.ThrowArgument_SetSliceNoRange(nameof(values)); - srcSpan = tensor; - } - else - srcSpan = tensor.Slice(ranges); - - if (!TensorHelpers.IsContiguousAndDense(srcSpan)) - ThrowHelper.ThrowArgument_SetSliceInvalidShapes(nameof(values)); - - if (!TensorHelpers.IsBroadcastableTo(values.Lengths, srcSpan.Lengths)) - ThrowHelper.ThrowArgument_SetSliceInvalidShapes(nameof(values)); - - values.CopyTo(srcSpan); - - return ref tensor; - } - #endregion - - #region Split - /// - /// Split a into along the given . If the tensor cannot be split - /// evenly on the given an exception is thrown. - /// - /// Input . - /// How many times to split the - /// The axis to split on. - public static Tensor[] Split(scoped in ReadOnlyTensorSpan tensor, int splitCount, nint dimension) - { - if (tensor.Lengths[(int)dimension] % splitCount != 0) - ThrowHelper.ThrowArgument_SplitNotSplitEvenly(); - - Tensor[] outputs = new Tensor[splitCount]; - - nint totalToCopy = tensor.FlattenedLength / splitCount; - nint copyLength = 1; - for (nint i = dimension; i < tensor.Lengths.Length; i++) - { - copyLength *= tensor.Lengths[(int)i]; - } - copyLength /= splitCount; - nint[] newShape = tensor.Lengths.ToArray(); - newShape[(int)dimension] = newShape[(int)dimension] / splitCount; - - scoped Span oIndices; - nint[]? oIndicesArray; - scoped Span iIndices; - nint[]? iIndicesArray; - if (tensor.Rank > 6) - { - oIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - oIndices = oIndicesArray.AsSpan(0, tensor.Rank); - oIndices.Clear(); - - iIndicesArray = ArrayPool.Shared.Rent(tensor.Rank); - iIndices = iIndicesArray.AsSpan(0, tensor.Rank); - iIndices.Clear(); - } - else - { - oIndicesArray = null; - oIndices = stackalloc nint[tensor.Rank]; - iIndicesArray = null; - iIndices = stackalloc nint[tensor.Rank]; - } - - for (int i = 0; i < outputs.Length; i++) - { - T[] values = new T[(int)totalToCopy]; - outputs[i] = new Tensor(values, newShape, memoryOffset: 0); - oIndices.Clear(); - iIndices.Clear(); - - iIndices[(int)dimension] = i; - ReadOnlyTensorSpan islice = tensor.Slice(tensor.Lengths); - TensorSpan oslice = outputs[i].AsTensorSpan().Slice(outputs[i]._lengths); - - nint copiedValues = 0; - while (copiedValues < totalToCopy) - { - TensorSpanHelpers.Memmove(ref Unsafe.Add(ref oslice._reference, TensorSpanHelpers.ComputeLinearIndex(oIndices, outputs[0].Strides, outputs[0].Lengths)), ref Unsafe.Add(ref islice._reference, TensorSpanHelpers.ComputeLinearIndex(iIndices, islice.Strides, islice.Lengths)), copyLength); - TensorSpanHelpers.AdjustIndexes((int)dimension, 1, oIndices, outputs[i]._lengths); - TensorSpanHelpers.AdjustIndexes((int)dimension - 1, 1, iIndices, tensor.Lengths); - copiedValues += copyLength; - } - } - - if (oIndicesArray != null && iIndicesArray != null) - { - ArrayPool.Shared.Return(oIndicesArray); - ArrayPool.Shared.Return(iIndicesArray); - } - - return outputs; - } - #endregion - - #region Squeeze - /// - /// Removes all dimensions of length one from the . - /// - /// The to remove all dimensions of length 1. - public static Tensor Squeeze(this Tensor tensor) - { - return SqueezeDimension(tensor, -1); - } - - /// - /// Removes axis of length one from the for the given . - /// If the dimension is not of length one it will throw an exception. - /// - /// The to remove dimension of length 1. - /// The dimension to remove. - public static Tensor SqueezeDimension(this Tensor tensor, int dimension) - { - if (dimension >= tensor.Rank) - ThrowHelper.ThrowArgument_AxisLargerThanRank(); - - nint[] lengths; - nint[] strides; - - List tempLengths = new List(); - if (dimension == -1) - { - for (int i = 0; i < tensor.Lengths.Length; i++) - { - if (tensor.Lengths[i] != 1) - { - tempLengths.Add(tensor.Lengths[i]); - } - } - lengths = tempLengths.ToArray(); - strides = TensorSpanHelpers.CalculateStrides(lengths); - } - else - { - if (tensor.Lengths[dimension] != 1) - { - ThrowHelper.ThrowArgument_InvalidSqueezeAxis(); - } - for (int i = 0; i < tensor.Lengths.Length; i++) - { - if (i != dimension) - { - tempLengths.Add(tensor.Lengths[i]); - } - } - lengths = tempLengths.ToArray(); - strides = TensorSpanHelpers.CalculateStrides(lengths); - } - - return new Tensor(tensor._values, lengths, strides, tensor._memoryOffset); - } - - /// - /// Removes all dimensions of length one from the . - /// - /// The to remove all dimensions of length 1. - public static TensorSpan Squeeze(in this TensorSpan tensor) - { - return SqueezeDimension(tensor, -1); - } - - /// - /// Removes axis of length one from the for the given . - /// If the dimension is not of length one it will throw an exception. - /// - /// The to remove dimension of length 1. - /// The dimension to remove. - public static TensorSpan SqueezeDimension(in this TensorSpan tensor, int dimension) - { - if (dimension >= tensor.Rank) - ThrowHelper.ThrowArgument_AxisLargerThanRank(); - - nint[] lengths; - nint[] strides; - - List tempLengths = new List(); - if (dimension == -1) - { - for (int i = 0; i < tensor.Lengths.Length; i++) - { - if (tensor.Lengths[i] != 1) - { - tempLengths.Add(tensor.Lengths[i]); - } - } - lengths = tempLengths.ToArray(); - strides = TensorSpanHelpers.CalculateStrides(lengths); - } - else - { - if (tensor.Lengths[dimension] != 1) - { - ThrowHelper.ThrowArgument_InvalidSqueezeAxis(); - } - for (int i = 0; i < tensor.Lengths.Length; i++) - { - if (i != dimension) - { - tempLengths.Add(tensor.Lengths[i]); - } - } - lengths = tempLengths.ToArray(); - strides = TensorSpanHelpers.CalculateStrides(lengths); - } - - return new TensorSpan(ref tensor._reference, lengths, strides, tensor._shape._memoryLength); - } - - /// - /// Removes all dimensions of length one from the . - /// - /// The to remove all dimensions of length 1. - public static ReadOnlyTensorSpan Squeeze(in this ReadOnlyTensorSpan tensor) - { - return SqueezeDimension(tensor, -1); - } - - /// - /// Removes axis of length one from the for the given . - /// If the dimension is not of length one it will throw an exception. - /// - /// The to remove dimension of length 1. - /// The dimension to remove. - public static ReadOnlyTensorSpan SqueezeDimension(in this ReadOnlyTensorSpan tensor, int dimension) - { - if (dimension >= tensor.Rank) - ThrowHelper.ThrowArgument_AxisLargerThanRank(); - - nint[] lengths; - nint[] strides; - - List tempLengths = new List(); - if (dimension == -1) - { - for (int i = 0; i < tensor.Lengths.Length; i++) - { - if (tensor.Lengths[i] != 1) - { - tempLengths.Add(tensor.Lengths[i]); - } - } - lengths = tempLengths.ToArray(); - strides = TensorSpanHelpers.CalculateStrides(lengths); - } - else - { - if (tensor.Lengths[dimension] != 1) - { - ThrowHelper.ThrowArgument_InvalidSqueezeAxis(); - } - for (int i = 0; i < tensor.Lengths.Length; i++) - { - if (i != dimension) - { - tempLengths.Add(tensor.Lengths[i]); - } - } - lengths = tempLengths.ToArray(); - strides = TensorSpanHelpers.CalculateStrides(lengths); - } - - return new ReadOnlyTensorSpan(ref tensor._reference, lengths, strides, tensor._shape._memoryLength); - } - #endregion - - #region Stack - /// - /// Join multiple along a new dimension that is added at position 0. All tensors must have the same shape. - /// - /// Input . - public static Tensor Stack(params ReadOnlySpan> tensors) - { - return StackAlongDimension(0, tensors); - } - - /// - /// Join multiple along a new dimension. The axis parameter specifies the index of the new dimension. All tensors must have the same shape. - /// - /// Input . - /// Index of where the new dimension will be. - public static Tensor StackAlongDimension(int dimension, params ReadOnlySpan> tensors) - { - if (tensors.Length < 2) - ThrowHelper.ThrowArgument_StackTooFewTensors(); - - for (int i = 1; i < tensors.Length; i++) - { - if (!TensorHelpers.AreLengthsTheSame(tensors[0], tensors[i])) - ThrowHelper.ThrowArgument_StackShapesNotSame(); - } - - if (dimension < 0) - dimension = tensors[0].Rank - dimension; - - Tensor[] outputs = new Tensor[tensors.Length]; - for (int i = 0; i < tensors.Length; i++) - { - outputs[i] = Tensor.Unsqueeze(tensors[i], dimension); - } - return Tensor.ConcatenateOnDimension(dimension, outputs); - } - - /// - /// Join multiple along a new dimension that is added at position 0. All tensors must have the same shape. - /// - /// Input . - /// - public static ref readonly TensorSpan Stack(scoped in ReadOnlySpan> tensors, in TensorSpan destination) - { - return ref StackAlongDimension(tensors, destination, 0); - } - - /// - /// Join multiple along a new dimension. The axis parameter specifies the index of the new dimension. All tensors must have the same shape. - /// - /// Input . - /// - /// Index of where the new dimension will be. - public static ref readonly TensorSpan StackAlongDimension(scoped ReadOnlySpan> tensors, in TensorSpan destination, int dimension) - { - if (tensors.Length < 2) - ThrowHelper.ThrowArgument_StackTooFewTensors(); - - for (int i = 1; i < tensors.Length; i++) - { - if (!TensorHelpers.AreLengthsTheSame(tensors[0], tensors[i])) - ThrowHelper.ThrowArgument_StackShapesNotSame(); - } - - if (dimension < 0) - dimension = tensors[0].Rank - dimension; - - Tensor[] outputs = new Tensor[tensors.Length]; - for (int i = 0; i < tensors.Length; i++) - { - outputs[i] = Tensor.Unsqueeze(tensors[i], dimension); - } - return ref Tensor.ConcatenateOnDimension(dimension, outputs, destination); - } - #endregion - - #region ToString - /// - /// Creates a representation of the ."/> - /// - /// The you want to represent as a string. - /// Maximum Length of each dimension - /// A representation of the - public static string ToString(this in TensorSpan tensor, params ReadOnlySpan maximumLengths) => - ((ReadOnlyTensorSpan)tensor).ToString(maximumLengths); - - /// - /// Creates a representation of the ."/> - /// - /// - /// The you want to represent as a string. - /// Maximum Length of each dimension - public static string ToString(this in ReadOnlyTensorSpan tensor, params ReadOnlySpan maximumLengths) - { - StringBuilder sb = new(); - ToString(in tensor, sb, maximumLengths); - return sb.ToString(); - } - - internal static void ToString(this in ReadOnlyTensorSpan tensor, StringBuilder sb, params ReadOnlySpan maximumLengths) - { - if (maximumLengths.Length != tensor.Rank) - ThrowHelper.ThrowArgument_DimensionsNotSame(nameof(tensor)); - - scoped Span curIndexes; - nint[]? curIndexesArray; - if (tensor.Rank > 6) - { - curIndexesArray = ArrayPool.Shared.Rent(tensor.Rank); - curIndexes = curIndexesArray.AsSpan(0, tensor.Rank); - curIndexes.Clear(); - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[tensor.Rank]; - } - - nint copiedValues = 0; - - T[] values = new T[tensor.Lengths[tensor.Rank - 1]]; - while (copiedValues < tensor.FlattenedLength) - { - var sp = new ReadOnlyTensorSpan(ref Unsafe.Add(ref tensor._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, tensor.Strides, tensor.Lengths)), [tensor.Lengths[tensor.Rank - 1]], [1], tensor.Lengths[tensor.Rank - 1]); - sb.Append('{'); - sp.FlattenTo(values); - sb.Append(string.Join(",", values)); - sb.AppendLine("}"); - - TensorSpanHelpers.AdjustIndexes(tensor.Rank - 2, 1, curIndexes, tensor.Lengths); - copiedValues += tensor.Lengths[tensor.Rank - 1]; - } - - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); - } - - /// - /// Creates a representation of the ."/> - /// - /// The you want to represent as a string. - /// Maximum Length of each dimension - /// A representation of the - public static string ToString(this Tensor tensor, params ReadOnlySpan maximumLengths) => ((ReadOnlyTensorSpan)tensor).ToString(maximumLengths); - - #endregion - - #region Transpose - /// - /// Swaps the last two dimensions of the tensor. - /// - /// Input . - public static Tensor Transpose(Tensor tensor) - { - if (tensor.Lengths.Length < 2) - ThrowHelper.ThrowArgument_TransposeTooFewDimensions(); - - Span dimension = tensor.Rank <= TensorShape.MaxInlineRank ? stackalloc int[tensor.Rank] : new int[tensor.Rank]; - TensorSpanHelpers.FillRange(dimension); - - int temp = dimension[tensor.Rank - 1]; - dimension[tensor.Rank - 1] = dimension[tensor.Rank - 2]; - dimension[tensor.Rank - 2] = temp; - - return PermuteDimensions(tensor, dimension); - } - #endregion - - #region TryBroadcastTo - /// - /// Broadcast the data from to the smallest broadcastable shape compatible with and stores it in - /// If the shapes are not compatible, false is returned. - /// - /// Input . - /// Destination . - public static bool TryBroadcastTo(this Tensor tensor, in TensorSpan destination) - { - return TryBroadcastTo((ReadOnlyTensorSpan)tensor, destination); - } - - /// - /// Broadcast the data from to the smallest broadcastable shape compatible with and stores it in - /// If the shapes are not compatible, false is returned. - /// - /// Input . - /// Destination . - public static bool TryBroadcastTo(in this TensorSpan tensor, in TensorSpan destination) - { - return TryBroadcastTo((ReadOnlyTensorSpan)tensor, destination); - } - - /// - /// Broadcast the data from to the smallest broadcastable shape compatible with and stores it in - /// If the shapes are not compatible, false is returned. - /// - /// Input . - /// Destination . - public static bool TryBroadcastTo(in this ReadOnlyTensorSpan tensor, in TensorSpan destination) - { - if (!TensorHelpers.IsBroadcastableTo(tensor.Lengths, destination.Lengths)) - return false; - - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(tensor.Lengths, destination.Lengths); - if (!TensorHelpers.AreLengthsTheSame(destination.Lengths, newSize)) - return false; - - LazyBroadcast(tensor, newSize).CopyTo(destination); - return true; - } - #endregion - - #region Unsqueeze - /// - /// Insert a new dimension of length 1 that will appear at the dimension position. - /// - /// The to add a dimension of length 1. - /// The index of the dimension to add. - public static Tensor Unsqueeze(this Tensor tensor, int dimension) - { - if (dimension > tensor.Lengths.Length) - ThrowHelper.ThrowArgument_AxisLargerThanRank(); - if (dimension < 0) - dimension = tensor.Rank - dimension; - - Span lengths = tensor._lengths.Length + 1 <= TensorShape.MaxInlineRank ? - stackalloc nint[tensor._lengths.Length + 1] : - new nint[tensor._lengths.Length + 1]; - tensor._lengths.AsSpan(0, dimension).CopyTo(lengths); - tensor._lengths.AsSpan(dimension).CopyTo(lengths.Slice(dimension + 1)); - lengths[dimension] = 1; - - Span strides = tensor.Strides.Length + 1 <= TensorShape.MaxInlineRank ? - stackalloc nint[tensor.Strides.Length + 1] : - new nint[tensor.Strides.Length + 1]; - if (dimension == tensor.Rank) - { - tensor.Strides.CopyTo(strides); - strides[dimension] = tensor.Strides[dimension - 1]; - } - else - { - tensor.Strides.Slice(0, dimension).CopyTo(strides); - tensor.Strides.Slice(dimension).CopyTo(strides.Slice(dimension + 1)); - strides[dimension] = tensor.Strides[dimension] * tensor.Lengths[dimension]; - } - - return new Tensor(tensor._values, lengths, strides, tensor._memoryOffset); - } - - /// - /// Insert a new dimension of length 1 that will appear at the dimension position. - /// - /// The to add a dimension of length 1. - /// The index of the dimension to add. - public static TensorSpan Unsqueeze(in this TensorSpan tensor, int dimension) - { - if (dimension > tensor.Lengths.Length) - ThrowHelper.ThrowArgument_AxisLargerThanRank(); - if (dimension < 0) - dimension = tensor.Rank - dimension; - - Span lengths = tensor.Lengths.Length + 1 <= TensorShape.MaxInlineRank ? - stackalloc nint[tensor.Lengths.Length + 1] : - new nint[tensor.Lengths.Length + 1]; - tensor.Lengths.Slice(0, dimension).CopyTo(lengths); - tensor.Lengths.Slice(dimension).CopyTo(lengths.Slice(dimension + 1)); - lengths[dimension] = 1; - - Span strides = tensor.Strides.Length + 1 <= TensorShape.MaxInlineRank ? - stackalloc nint[tensor.Strides.Length + 1] : - new nint[tensor.Strides.Length + 1]; - if (dimension == tensor.Rank) - { - tensor.Strides.CopyTo(strides); - strides[dimension] = tensor.Strides[dimension - 1]; - } - else - { - tensor.Strides.Slice(0, dimension).CopyTo(strides); - tensor.Strides.Slice(dimension).CopyTo(strides.Slice(dimension + 1)); - strides[dimension] = tensor.Strides[dimension] * tensor.Lengths[dimension]; - } - - return new TensorSpan(ref tensor._reference, lengths, strides, tensor._shape._memoryLength); - } - - /// - /// Insert a new dimension of length 1 that will appear at the dimension position. - /// - /// The to add a dimension of length 1. - /// The index of the dimension to add. - public static ReadOnlyTensorSpan Unsqueeze(in this ReadOnlyTensorSpan tensor, int dimension) - { - if (dimension > tensor.Lengths.Length) - ThrowHelper.ThrowArgument_AxisLargerThanRank(); - if (dimension < 0) - dimension = tensor.Rank - dimension; - - Span lengths = tensor.Lengths.Length + 1 <= TensorShape.MaxInlineRank ? - stackalloc nint[tensor.Lengths.Length + 1] : - new nint[tensor.Lengths.Length + 1]; - tensor.Lengths.Slice(0, dimension).CopyTo(lengths); - tensor.Lengths.Slice(dimension).CopyTo(lengths.Slice(dimension + 1)); - lengths[dimension] = 1; - - Span strides = tensor.Strides.Length + 1 <= TensorShape.MaxInlineRank ? - stackalloc nint[tensor.Strides.Length + 1] : - new nint[tensor.Strides.Length + 1]; - if (dimension == tensor.Rank) - { - tensor.Strides.CopyTo(strides); - strides[dimension] = tensor.Strides[dimension - 1]; - } - else - { - tensor.Strides.Slice(0, dimension).CopyTo(strides); - tensor.Strides.Slice(dimension).CopyTo(strides.Slice(dimension + 1)); - strides[dimension] = tensor.Strides[dimension] * tensor.Lengths[dimension]; - } - - return new ReadOnlyTensorSpan(ref tensor._reference, lengths, strides, tensor._shape._memoryLength); - } - #endregion - - #region TensorPrimitives - #region Abs - /// - /// Takes the absolute value of each element of the and returns a new with the result. - /// - /// The to take the abs of. - public static Tensor Abs(in ReadOnlyTensorSpan x) - where T : INumberBase - { - Tensor output = Tensor.Create(x.Lengths); - Abs(x, output); - return output; - } - - /// - /// Takes the absolute value of each element of the and returns a new with the result. - /// - /// The to take the abs of. - /// The destination. - public static ref readonly TensorSpan Abs(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : INumberBase - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Abs); - } - #endregion - - #region Acos - /// - /// Takes the inverse cosine of each element of the and returns a new with the result. - /// - /// The to take the sin of. - public static Tensor Acos(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Acos(x, output); - return output; - } - - /// - /// Takes the inverse cosine of each element of the and returns a new with the result. - /// - /// The to take the sin of. - /// - public static ref readonly TensorSpan Acos(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Acos); - } - #endregion - - #region Acosh - /// - /// Takes the inverse hyperbolic cosine of each element of the and returns a new with the result. - /// - /// The to take the sin of. - public static Tensor Acosh(in ReadOnlyTensorSpan x) - where T : IHyperbolicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Acosh(x, output); - return output; - } - - /// - /// Takes the inverse hyperbolic cosine of each element of the and returns a new with the result. - /// - /// The to take the sin of. - /// - public static ref readonly TensorSpan Acosh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IHyperbolicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Acosh); - } - #endregion - - #region AcosPi - /// - /// Takes the inverse hyperbolic cosine divided by pi of each element of the and returns a new with the result. - /// - /// The to take the sin of. - public static Tensor AcosPi(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - AcosPi(x, output); - return output; - } - - /// - /// Takes the inverse hyperbolic cosine divided by pi of each element of the and returns a new with the result. - /// - /// The to take the sin of. - /// - public static ref readonly TensorSpan AcosPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.AcosPi); - } - #endregion - - #region Add - /// - /// Adds each element of to each element of and returns a new with the result. - /// - /// The of values to add. - /// The second of values to add. - public static Tensor Add(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IAdditionOperators, IAdditiveIdentity - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Add(x, y, output); - return output; - } - - /// - /// Adds to each element of and returns a new with the result. - /// - /// The of values to add. - /// The to add to each element of . - public static Tensor Add(in ReadOnlyTensorSpan x, T y) - where T : IAdditionOperators, IAdditiveIdentity - { - Tensor output = Tensor.Create(x.Lengths); - Add(x, y, output); - return output; - } - - /// - /// Adds each element of to each element of and returns a new with the result. - /// - /// The of values to add. - /// The second of values to add. - /// - public static ref readonly TensorSpan Add(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IAdditionOperators, IAdditiveIdentity - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Add); - } - - /// - /// Adds to each element of and returns a new with the result. - /// - /// The of values to add. - /// The to add to each element of . - /// - public static ref readonly TensorSpan Add(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IAdditionOperators, IAdditiveIdentity - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Add); - } - #endregion - - #region Asin - /// - /// Takes the inverse sin of each element of the and returns a new with the result. - /// - /// The to take the sin of. - public static Tensor Asin(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Asin(x, output); - return output; - } - - /// - /// Takes the inverse sin of each element of the and returns a new with the result. - /// - /// The to take the sin of. - /// - public static ref readonly TensorSpan Asin(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Asin); - } - #endregion - - #region Asinh - /// - /// Takes the inverse hyperbolic sine of each element of the and returns a new with the result. - /// - /// The to take the sin of. - public static Tensor Asinh(in ReadOnlyTensorSpan x) - where T : IHyperbolicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Asinh(x, output); - return output; - } - - /// - /// Takes the inverse hyperbolic sine of each element of the and returns a new with the result. - /// - /// The to take the sin of. - /// - public static ref readonly TensorSpan Asinh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IHyperbolicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Asinh); - } - #endregion - - #region AsinPi - /// - /// Takes the inverse hyperbolic sine divided by pi of each element of the and returns a new with the result. - /// - /// The to take the sin of. - public static Tensor AsinPi(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - AsinPi(x, output); - return output; - } - - /// - /// Takes the inverse hyperbolic sine divided by pi of each element of the and returns a new with the result. - /// - /// The to take the sin of. - /// - public static ref readonly TensorSpan AsinPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.AsinPi); - } - #endregion - - #region Atan - /// - /// Takes the arc tangent of each element of the and returns a new with the result. - /// - /// The input - public static Tensor Atan(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Atan(x, output); - return output; - } - - /// - /// Takes the arc tangent of each element of the and returns a new with the result. - /// - /// The input - /// - public static ref readonly TensorSpan Atan(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Atan); - } - #endregion - - #region Atan2 - /// - /// Takes the arc tangent of the two input and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor Atan2(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IFloatingPointIeee754 - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Atan2(x, y, output); - return output; - } - - /// - /// Takes the arc tangent of the two input and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Atan2(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Atan2); - } - - /// - /// Takes the arc tangent of the two input and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor Atan2(in ReadOnlyTensorSpan x, T y) - where T : IFloatingPointIeee754 - { - Tensor output = Tensor.Create(x.Lengths); - - Atan2(x, y, output); - return output; - } - - /// - /// Takes the arc tangent of the two input and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Atan2(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Atan2); - } - - /// - /// Takes the arc tangent of the two input and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor Atan2(T x, in ReadOnlyTensorSpan y) - where T : IFloatingPointIeee754 - { - Tensor output = Tensor.Create(y.Lengths); - - Atan2(x, y, output); - return output; - } - - /// - /// Takes the arc tangent of the two input and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Atan2(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperTInSpanInSpanOut(x, y, destination, TensorPrimitives.Atan2); - } - #endregion - - #region Atan2Pi - /// - /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor Atan2Pi(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IFloatingPointIeee754 - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Atan2Pi(x, y, output); - return output; - } - - /// - /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Atan2Pi(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Atan2Pi); - } - - /// - /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor Atan2Pi(in ReadOnlyTensorSpan x, T y) - where T : IFloatingPointIeee754 - { - Tensor output = Tensor.Create(x.Lengths); - - Atan2Pi(x, y, output); - return output; - } - - /// - /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Atan2Pi(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Atan2Pi); - } - - /// - /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor Atan2Pi(T x, in ReadOnlyTensorSpan y) - where T : IFloatingPointIeee754 - { - Tensor output = Tensor.Create(y.Lengths); - - Atan2Pi(x, y, output); - return output; - } - - /// - /// Takes the arc tangent of the two input , divides each element by pi, and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Atan2Pi(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperTInSpanInSpanOut(x, y, destination, TensorPrimitives.Atan2Pi); - - } - #endregion - - #region Atanh - /// - /// Takes the inverse hyperbolic tangent of each element of the and returns a new with the result. - /// - /// The input . - public static Tensor Atanh(in ReadOnlyTensorSpan x) - where T : IHyperbolicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Atanh(x, output); - return output; - } - - /// - /// Takes the inverse hyperbolic tangent of each element of the and returns a new with the result. - /// - /// The input . - /// - public static ref readonly TensorSpan Atanh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IHyperbolicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Atanh); - } - #endregion - - #region AtanPi - /// - /// Takes the inverse hyperbolic tangent divided by pi of each element of the and returns a new with the result. - /// - /// The input. - public static Tensor AtanPi(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - AtanPi(x, output); - return output; - } - - /// - /// Takes the inverse hyperbolic tangent divided by pi of each element of the and returns a new with the result. - /// - /// The input. - /// - public static ref readonly TensorSpan AtanPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.AtanPi); - } - #endregion - - #region Average - /// - /// Returns the average of the elements in the tensor. - /// - /// The to take the mean of. - /// representing the mean. - public static T Average(scoped in ReadOnlyTensorSpan x) - where T : INumberBase - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.Average); - } - #endregion - - #region BitwiseAnd - /// - /// Computes the element-wise bitwise and of the two input and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor BitwiseAnd(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IBitwiseOperators - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - BitwiseAnd(x, y, output); - return output; - } - - /// - /// Computes the element-wise bitwise and of the two input and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan BitwiseAnd(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IBitwiseOperators - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.BitwiseAnd); - } - - /// - /// Computes the element-wise bitwise and of the two input and returns a new with the result. - /// - /// The left . - /// The second value. - public static Tensor BitwiseAnd(in ReadOnlyTensorSpan x, T y) - where T : IBitwiseOperators - { - Tensor output = Tensor.Create(x.Lengths); - - BitwiseAnd(x, y, output); - return output; - } - - /// - /// Computes the element-wise bitwise and of the two input and returns a new with the result. - /// - /// The left . - /// The second value. - /// - public static ref readonly TensorSpan BitwiseAnd(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IBitwiseOperators - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.BitwiseAnd); - } - #endregion - - #region BitwiseOr - /// - /// Computes the element-wise bitwise of of the two input and returns a new with the result. - /// - /// The left . - /// The right . - public static Tensor BitwiseOr(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IBitwiseOperators - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - BitwiseOr(x, y, output); - return output; - } - - /// - /// Computes the element-wise bitwise of of the two input and returns a new with the result. - /// - /// The left . - /// The right . - /// - public static ref readonly TensorSpan BitwiseOr(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IBitwiseOperators - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.BitwiseOr); - } - - /// - /// Computes the element-wise bitwise or of the two input and returns a new with the result. - /// - /// The left . - /// The second value. - public static Tensor BitwiseOr(in ReadOnlyTensorSpan x, T y) - where T : IBitwiseOperators - { - Tensor output = Tensor.Create(x.Lengths); - - BitwiseOr(x, y, output); - return output; - } - - /// - /// Computes the element-wise bitwise or of the two input and returns a new with the result. - /// - /// The left . - /// The second value. - /// - public static ref readonly TensorSpan BitwiseOr(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IBitwiseOperators - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.BitwiseOr); - } - #endregion - - #region CubeRoot - /// - /// Computes the element-wise cube root of the input and returns a new with the result. - /// - /// The left . - public static Tensor Cbrt(in ReadOnlyTensorSpan x) - where T : IRootFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Cbrt(x, output); - return output; - } - - /// - /// Computes the element-wise cube root of the input and returns a new with the result. - /// - /// The left . - /// - public static ref readonly TensorSpan Cbrt(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IRootFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Cbrt); - } - #endregion - - #region Ceiling - /// - /// Computes the element-wise ceiling of the input and returns a new with the result. - /// - /// The left . - public static Tensor Ceiling(in ReadOnlyTensorSpan x) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Ceiling(x, output); - return output; - } - - /// - /// Computes the element-wise ceiling of the input and returns a new with the result. - /// - /// The left . - /// - public static ref readonly TensorSpan Ceiling(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IFloatingPoint - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Ceiling); - } - #endregion - - #region ConvertChecked - /// - /// Copies to a new converting each - /// value to a value. - /// - /// The input . - public static Tensor ConvertChecked(in ReadOnlyTensorSpan source) - where TFrom : IEquatable, IEqualityOperators, INumberBase - where TTo : INumberBase - { - Tensor output = Tensor.Create(source.Lengths); - - ConvertChecked(source, output); - return output; - } - - /// - /// Copies to a new converting each - /// value to a value. - /// - /// The input . - /// - public static ref readonly TensorSpan ConvertChecked(scoped in ReadOnlyTensorSpan source, in TensorSpan destination) - where TFrom : IEquatable, IEqualityOperators, INumberBase - where TTo : INumberBase - { - return ref TensorPrimitivesHelperSpanInSpanOut(source, destination, TensorPrimitives.ConvertChecked); - } - #endregion - - #region ConvertSaturating - /// - /// Copies to a new converting each - /// value to a value. - /// - /// The input . - public static Tensor ConvertSaturating(in ReadOnlyTensorSpan source) - where TFrom : IEquatable, IEqualityOperators, INumberBase - where TTo : INumberBase - { - Tensor output = Tensor.Create(source.Lengths); - - ConvertSaturating(source, output); - return output; - } - - /// - /// Copies to a new converting each - /// value to a value. - /// - /// The input . - /// - public static ref readonly TensorSpan ConvertSaturating(scoped in ReadOnlyTensorSpan source, in TensorSpan destination) - where TFrom : IEquatable, IEqualityOperators, INumberBase - where TTo : INumberBase - { - return ref TensorPrimitivesHelperSpanInSpanOut(source, destination, TensorPrimitives.ConvertSaturating); - } - #endregion - - #region ConvertTruncating - /// - /// Copies to a new converting each - /// value to a value. - /// - /// The input . - public static Tensor ConvertTruncating(in ReadOnlyTensorSpan source) - where TFrom : IEquatable, IEqualityOperators, INumberBase - where TTo : INumberBase - { - Tensor output = Tensor.Create(source.Lengths); - - ConvertTruncating(source, output); - return output; - } - - /// - /// Copies to a new converting each - /// value to a value. - /// - /// The input . - /// - public static ref readonly TensorSpan ConvertTruncating(scoped in ReadOnlyTensorSpan source, in TensorSpan destination) - where TFrom : IEquatable, IEqualityOperators, INumberBase - where TTo : INumberBase - { - return ref TensorPrimitivesHelperSpanInSpanOut(source, destination, TensorPrimitives.ConvertTruncating); - } - #endregion - - #region CopySign - /// - /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new tensor with the result. - /// - /// Input . - /// The number with the associated sign. - public static Tensor CopySign(in ReadOnlyTensorSpan x, T sign) - where T : INumber - { - Tensor output = Create(x.Lengths); - - CopySign(x, sign, output); - return output; - } - - /// - /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new with the result. - /// - /// Input . - /// The with the associated signs. - public static Tensor CopySign(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan sign) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(sign.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, sign.Lengths)); - } - - CopySign(x, sign, output); - return output; - } - - /// - /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new tensor with the result. - /// - /// Input . - /// The number with the associated sign. - /// - public static ref readonly TensorSpan CopySign(scoped in ReadOnlyTensorSpan x, T sign, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, sign, destination, TensorPrimitives.CopySign); - } - - /// - /// Computes the element-wise result of copying the sign from one number to another number in the specified tensors and returns a new with the result. - /// - /// Input . - /// The with the associated signs. - /// - public static ref readonly TensorSpan CopySign(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan sign, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, sign, destination, TensorPrimitives.CopySign); - } - #endregion - - #region Cos - /// - /// Takes the cosine of each element of the and returns a new with the result. - /// - /// The to take the cosine of. - public static Tensor Cos(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Cos(x, output); - return output; - } - - /// - /// Takes the cosine of each element of the and returns a new with the result. - /// - /// The to take the cosine of. - /// - public static ref readonly TensorSpan Cos(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Cos); - } - #endregion - - #region Cosh - /// - /// Takes the hyperbolic cosine of each element of the and returns a new with the result. - /// - /// The to take the cosine of. - public static Tensor Cosh(in ReadOnlyTensorSpan x) - where T : IHyperbolicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Cosh(x, output); - return output; - } - - /// - /// Takes the hyperbolic cosine of each element of the and returns a new with the result. - /// - /// The to take the cosine of. - /// - public static ref readonly TensorSpan Cosh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IHyperbolicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Cosh); - } - #endregion - - #region CosineSimilarity - /// - /// Compute cosine similarity between and . - /// - /// The first - /// The second - public static Tensor CosineSimilarity(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IRootFunctions - { - if (x.Rank != 2) - ThrowHelper.ThrowArgument_2DTensorRequired(nameof(x)); - - if (y.Rank != 2) - ThrowHelper.ThrowArgument_2DTensorRequired(nameof(y)); - - if (x.Lengths[1] != y.Lengths[1]) - ThrowHelper.ThrowArgument_IncompatibleDimensions(x.Lengths[1], y.Lengths[1]); - - nint dim1 = x.Lengths[0]; - nint dim2 = y.Lengths[0]; - - T[] values = new T[dim1 * dim2]; - - Tensor output = Tensor.Create(values, [dim1, dim2]); - - CosineSimilarity(x, y, output); - - return output; - } - - /// - /// Compute cosine similarity between and . - /// - /// The first - /// The second - /// - public static ref readonly TensorSpan CosineSimilarity(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IRootFunctions - { - if (x.Rank != 2) - ThrowHelper.ThrowArgument_2DTensorRequired(nameof(x)); - - if (y.Rank != 2) - ThrowHelper.ThrowArgument_2DTensorRequired(nameof(y)); - - if (x.Lengths[1] != y.Lengths[1]) - ThrowHelper.ThrowArgument_IncompatibleDimensions(x.Lengths[1], y.Lengths[1]); - - nint dim1 = x.Lengths[0]; - nint dim2 = y.Lengths[0]; - - if (destination.Lengths[0] != dim1 || destination.Lengths[1] != dim2) - ThrowHelper.ThrowArgument_IncompatibleDimensions(x.Lengths[1], y.Lengths[1]); - - Span values = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - - scoped Span leftIndexes = stackalloc nint[2]; - scoped Span rightIndexes = stackalloc nint[2]; - - int outputOffset = 0; - - ReadOnlySpan lspan; - ReadOnlySpan rspan; - int rowLength = (int)x.Lengths[1]; - for (int i = 0; i < dim1; i++) - { - for (int j = 0; j < dim2; j++) - { - lspan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref x._reference, TensorSpanHelpers.ComputeLinearIndex(leftIndexes, x.Strides, x.Lengths)), (int)rowLength); - rspan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref y._reference, TensorSpanHelpers.ComputeLinearIndex(rightIndexes, y.Strides, y.Lengths)), (int)rowLength); - values[outputOffset++] = TensorPrimitives.CosineSimilarity(lspan, rspan); - rightIndexes[0]++; - } - rightIndexes[0] = 0; - leftIndexes[0]++; - } - - return ref destination; - - } - #endregion - - #region CosPi - /// Computes the element-wise cosine of the value in the specified tensor that has been multiplied by Pi and returns a new with the results. - /// The input - /// - /// - /// This method effectively computes .CosPi([i]). - /// - /// - /// The angles in x must be in radians. Use or multiply by .Pi/180 to convert degrees to radians. - /// - /// - /// This method may call into the underlying C runtime or employ instructions specific to the current architecture. Exact results may differ between different - /// operating systems or architectures. - /// - /// - public static Tensor CosPi(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - CosPi(x, output); - return output; - } - - /// Computes the element-wise cosine of the value in the specified tensor that has been multiplied by Pi and returns a new with the results. - /// The input - /// - /// - /// - /// This method effectively computes .CosPi([i]). - /// - /// - /// The angles in x must be in radians. Use or multiply by .Pi/180 to convert degrees to radians. - /// - /// - /// This method may call into the underlying C runtime or employ instructions specific to the current architecture. Exact results may differ between different - /// operating systems or architectures. - /// - /// - public static ref readonly TensorSpan CosPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.CosPi); - } - #endregion - - #region DegreesToRadians - /// - /// Computes the element-wise conversion of each number of degrees in the specified tensor to radians and returns a new tensor with the results. - /// - /// The input . - public static Tensor DegreesToRadians(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - DegreesToRadians(x, output); - return output; - } - - /// - /// Computes the element-wise conversion of each number of degrees in the specified tensor to radians and returns a new tensor with the results. - /// - /// The input . - /// - public static ref readonly TensorSpan DegreesToRadians(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.DegreesToRadians); - } - #endregion - - #region Distance - /// - /// Computes the distance between two points, specified as non-empty, equal-length tensors of numbers, in Euclidean space. - /// - /// The input . - /// The input . - public static T Distance(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y) - where T : IRootFunctions - { - return TensorPrimitivesHelperTwoSpanInTOut(x, y, TensorPrimitives.Distance); - } - #endregion - - #region Divide - /// - /// Divides each element of by and returns a new with the result. - /// - /// Input . - /// The divisor - public static Tensor Divide(in ReadOnlyTensorSpan x, T y) - where T : IDivisionOperators - { - Tensor output = Create(x.Lengths); - Divide(x, y, output); - return output; - } - - /// - /// Divides by each element of and returns a new with the result."/> - /// - /// The value to be divided. - /// The divisor. - public static Tensor Divide(T x, in ReadOnlyTensorSpan y) - where T : IDivisionOperators - { - Tensor output = Tensor.Create(y.Lengths); - Divide(x, y, output); - return output; - } - - /// - /// Divides each element of by its corresponding element in and returns - /// a new with the result. - /// - /// The to be divided. - /// The divisor. - public static Tensor Divide(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IDivisionOperators - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Divide(x, y, output); - return output; - } - - /// - /// Divides each element of by and returns a new with the result. - /// - /// Input . - /// The divisor - /// - public static ref readonly TensorSpan Divide(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IDivisionOperators - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Divide); - } - - /// - /// Divides by each element of and returns a new with the result."/> - /// - /// The value to be divided. - /// The divisor. - /// - public static ref readonly TensorSpan Divide(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IDivisionOperators - { - return ref TensorPrimitivesHelperTInSpanInSpanOut(x, y, destination, TensorPrimitives.Divide); - } - - /// - /// Divides each element of by its corresponding element in and returns - /// a new with the result. - /// - /// The to be divided. - /// The divisor. - /// - public static ref readonly TensorSpan Divide(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IDivisionOperators - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Divide); - } - #endregion - - #region Dot - /// - /// Computes the dot product of two tensors containing numbers. - /// - /// The input . - /// The input . - public static T Dot(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IAdditionOperators, IAdditiveIdentity, IMultiplicativeIdentity, IMultiplyOperators - { - return TensorPrimitivesHelperTwoSpanInTOut(x, y, TensorPrimitives.Dot); - } - #endregion - - #region Exp - /// - /// Computes the element-wise result of raising e to the single-precision floating-point number powers in the specified tensor. - /// - /// The input . - public static Tensor Exp(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Exp(x, output); - return output; - } - - /// - /// Computes the element-wise result of raising e to the single-precision floating-point number powers in the specified tensor. - /// - /// The input . - /// - public static ref readonly TensorSpan Exp(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Exp); - } - #endregion - - #region Exp10 - /// - /// Computes the element-wise result of raising 10 to the number powers in the specified tensor. - /// - /// The input . - public static Tensor Exp10(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Exp10(x, output); - return output; - } - - /// - /// Computes the element-wise result of raising 10 to the number powers in the specified tensor. - /// - /// The input . - /// - public static ref readonly TensorSpan Exp10(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Exp10); - } - #endregion - - #region Exp10M1 - /// Computes the element-wise result of raising 10 to the number powers in the specified tensor, minus one. - /// The input . - public static Tensor Exp10M1(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Exp10M1(x, output); - return output; - } - - /// Computes the element-wise result of raising 10 to the number powers in the specified tensor, minus one. - /// The input . - /// - public static ref readonly TensorSpan Exp10M1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Exp10M1); - } - #endregion - - #region Exp2 - /// Computes the element-wise result of raising 2 to the number powers in the specified tensor. - /// The input . - public static Tensor Exp2(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Exp2(x, output); - return output; - } - - /// Computes the element-wise result of raising 2 to the number powers in the specified tensor. - /// The input . - /// - public static ref readonly TensorSpan Exp2(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Exp2); - } - #endregion - - #region Exp2M1 - /// Computes the element-wise result of raising 2 to the number powers in the specified tensor, minus one. - /// The input . - public static Tensor Exp2M1(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Exp2M1(x, output); - return output; - } - - /// Computes the element-wise result of raising 2 to the number powers in the specified tensor, minus one. - /// The input . - /// - public static ref readonly TensorSpan Exp2M1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Exp2M1); - } - #endregion - - #region ExpM1 - /// Computes the element-wise result of raising e to the number powers in the specified tensor, minus 1. - /// The input . - public static Tensor ExpM1(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - ExpM1(x, output); - return output; - } - - /// Computes the element-wise result of raising e to the number powers in the specified tensor, minus 1. - /// The input . - /// - public static ref readonly TensorSpan ExpM1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.ExpM1); - } - #endregion - - #region Floor - /// Computes the element-wise floor of numbers in the specified tensor. - /// The input . - public static Tensor Floor(in ReadOnlyTensorSpan x) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Floor(x, output); - return output; - } - - /// Computes the element-wise floor of numbers in the specified tensor. - /// The input . - /// - public static ref readonly TensorSpan Floor(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IFloatingPoint - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Floor); - } - #endregion - - #region Hypotenuse - /// - /// Computes the element-wise hypotenuse given values from two tensors representing the lengths of the shorter sides in a right-angled triangle. - /// If the shapes are not the same they are broadcast to the smallest compatible shape. - /// - /// Left . - /// Right . - public static Tensor Hypot(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IRootFunctions - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Hypot(x, y, output); - return output; - } - - /// - /// Computes the element-wise hypotenuse given values from two tensors representing the lengths of the shorter sides in a right-angled triangle. - /// If the shapes are not the same they are broadcast to the smallest compatible shape. - /// - /// Left . - /// Right . - /// - public static ref readonly TensorSpan Hypot(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IRootFunctions - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Hypot); - } - #endregion - - #region Ieee754Remainder - /// Computes the element-wise remainder of the numbers in the specified tensors. - /// If the shapes are not the same they are broadcast to the smallest compatible shape. - /// Left . - /// Right . - public static Tensor Ieee754Remainder(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IFloatingPointIeee754 - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Ieee754Remainder(x, y, output); - return output; - } - - /// Computes the element-wise remainder of the numbers in the specified tensors. - /// If the shapes are not the same they are broadcast to the smallest compatible shape. - /// Left . - /// Right . - /// - public static ref readonly TensorSpan Ieee754Remainder(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Ieee754Remainder); - } - - /// Computes the element-wise remainder of the numbers in the specified tensors. - /// The left . - /// The right . - public static Tensor Ieee754Remainder(in ReadOnlyTensorSpan x, T y) - where T : IFloatingPointIeee754 - { - Tensor output = Tensor.Create(x.Lengths); - - Ieee754Remainder(x, y, output); - return output; - } - - /// Computes the element-wise remainder of the numbers in the specified tensors. - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Ieee754Remainder(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Ieee754Remainder); - } - - /// Computes the element-wise remainder of the numbers in the specified tensors. - /// The left . - /// The right . - public static Tensor Ieee754Remainder(T x, in ReadOnlyTensorSpan y) - where T : IFloatingPointIeee754 - { - Tensor output = Tensor.Create(y.Lengths); - - Ieee754Remainder(x, y, output); - return output; - } - - /// Computes the element-wise remainder of the numbers in the specified tensors. - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Ieee754Remainder(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperTInSpanInSpanOut(x, y, destination, TensorPrimitives.Ieee754Remainder); - } - #endregion - - #region ILogB - /// Computes the element-wise integer logarithm of numbers in the specified tensor. - /// The input . - public static Tensor ILogB(in ReadOnlyTensorSpan x) - where T : IFloatingPointIeee754 - { - Tensor output = Tensor.Create(x.Lengths, x.Strides); - ILogB(x, output); - return output; - } - - /// Computes the element-wise integer logarithm of numbers in the specified tensor. - /// The input . - /// - public static ref readonly TensorSpan ILogB(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IFloatingPointIeee754 - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.ILogB); - } - #endregion - - #region IndexOfMax - /// Searches for the index of the largest number in the specified tensor. - /// The input . - public static int IndexOfMax(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - return TensorPrimitives.IndexOfMax(span); - } - #endregion - - #region IndexOfMaxMagnitude - /// Searches for the index of the number with the largest magnitude in the specified tensor. - /// The input . - public static int IndexOfMaxMagnitude(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - return TensorPrimitives.IndexOfMaxMagnitude(span); - } - #endregion - - #region IndexOfMin - /// Searches for the index of the smallest number in the specified tensor. - /// The input . - public static int IndexOfMin(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - return TensorPrimitives.IndexOfMin(span); - } - #endregion - - #region IndexOfMinMagnitude - /// - /// Searches for the index of the number with the smallest magnitude in the specified tensor. - /// - /// The input . - public static int IndexOfMinMagnitude(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - return TensorPrimitives.IndexOfMinMagnitude(span); - } - #endregion - - #region LeadingZeroCount - /// - /// Computes the element-wise leading zero count of numbers in the specified tensor. - /// - /// The input . - public static Tensor LeadingZeroCount(in ReadOnlyTensorSpan x) - where T : IBinaryInteger - { - Tensor output = Tensor.Create(x.Lengths); - LeadingZeroCount(x, output); - return output; - } - - /// - /// Computes the element-wise leading zero count of numbers in the specified tensor. - /// - /// The input . - /// - public static ref readonly TensorSpan LeadingZeroCount(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IBinaryInteger - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.LeadingZeroCount); - } - #endregion - - #region Log - /// - /// Takes the natural logarithm of each element of the and returns a new with the result. - /// - /// The to take the natural logarithm of. - public static Tensor Log(in ReadOnlyTensorSpan x) - where T : ILogarithmicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Log(x, output); - return output; - } - - /// - /// Takes the natural logarithm of each element of the and returns a new with the result. - /// - /// The to take the natural logarithm of. - /// - public static ref readonly TensorSpan Log(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Log); - } - - /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. - /// The first tensor - /// The second tensor - public static Tensor Log(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : ILogarithmicFunctions - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Log(x, y, output); - return output; - } - - /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. - /// The first tensor - /// The second tensor - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan Log(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Log); - } - - /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. - /// The first tensor - /// The second tensor - public static Tensor Log(in ReadOnlyTensorSpan x, T y) - where T : ILogarithmicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - - Log(x, y, output); - return output; - } - - /// Computes the element-wise logarithm of the numbers in a specified tensor to the specified base in another specified tensor. - /// The first tensor - /// The second tensor - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan Log(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Log); - } - #endregion - - #region Log10 - /// - /// Takes the base 10 logarithm of each element of the and returns a new with the result. - /// - /// The to take the base 10 logarithm of. - public static Tensor Log10(in ReadOnlyTensorSpan x) - where T : ILogarithmicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Log10(x, output); - return output; - } - - /// - /// Takes the base 10 logarithm of each element of the and returns a new with the result. - /// - /// The to take the base 10 logarithm of. - /// - public static ref readonly TensorSpan Log10(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Log10); - } - #endregion - - #region Log10P1 - /// - /// Takes the base 10 logarithm plus 1 of each element of the and returns a new with the result. - /// - /// The to take the base 10 logarithm of. - public static Tensor Log10P1(in ReadOnlyTensorSpan x) - where T : ILogarithmicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Log10P1(x, output); - return output; - } - - /// - /// Takes the base 10 logarithm plus 1 of each element of the and returns a new with the result. - /// - /// The to take the base 10 logarithm of. - /// - public static ref readonly TensorSpan Log10P1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Log10P1); - } - #endregion - - #region Log2 - /// - /// Takes the base 2 logarithm of each element of the and returns a new with the result. - /// - /// The to take the base 2 logarithm of. - public static Tensor Log2(in ReadOnlyTensorSpan x) - where T : ILogarithmicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Log2(x, output); - return output; - } - - /// - /// Takes the base 2 logarithm of each element of the and returns a new with the result. - /// - /// The to take the base 2 logarithm of. - /// - public static ref readonly TensorSpan Log2(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Log2); - } - #endregion - - #region Log2P1 - /// - /// Takes the base 2 logarithm plus 1 of each element of the and returns a new with the result. - /// - /// The to take the base 2 logarithm of. - public static Tensor Log2P1(in ReadOnlyTensorSpan x) - where T : ILogarithmicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Log2P1(x, output); - return output; - } - - /// - /// Takes the base 2 logarithm plus 1 of each element of the and returns a new with the result. - /// - /// The to take the base 2 logarithm of. - /// - public static ref readonly TensorSpan Log2P1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Log2P1); - } - #endregion - - #region LogP1 - /// - /// Takes the natural logarithm plus 1 of each element of the and returns a new with the result. - /// - /// The to take the natural logarithm of. - public static Tensor LogP1(in ReadOnlyTensorSpan x) - where T : ILogarithmicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - LogP1(x, output); - return output; - } - - /// - /// Takes the natural logarithm plus 1 of each element of the and returns a new with the result. - /// - /// The to take the natural logarithm of. - /// - public static ref readonly TensorSpan LogP1(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ILogarithmicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.LogP1); - } - #endregion - - #region Max - /// Searches for the largest number in the specified tensor. - /// The input .. - public static T Max(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.Max); - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor Max(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Max(x, y, output); - return output; - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan Max(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Max); - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor Max(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - Max(x, y, output); - return output; - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan Max(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Max); - } - #endregion - - #region MaxMagnitude - /// Searches for the number with the largest magnitude in the specified tensor. - /// The input .. - public static T MaxMagnitude(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.MaxMagnitude); - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MaxMagnitude(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - MaxMagnitude(x, y, output); - return output; - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MaxMagnitude(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.MaxMagnitude); - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MaxMagnitude(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - MaxMagnitude(x, y, output); - return output; - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MaxMagnitude(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.MaxMagnitude); - } - #endregion - - #region MaxMagnitudeNumber - /// Searches for the number with the largest magnitude in the specified tensor. - /// The input .. - public static T MaxMagnitudeNumber(scoped in ReadOnlyTensorSpan x) - where T : INumberBase - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.MaxMagnitudeNumber); - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MaxMagnitudeNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - MaxMagnitudeNumber(x, y, output); - return output; - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MaxMagnitudeNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.MaxMagnitudeNumber); - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MaxMagnitudeNumber(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - MaxMagnitudeNumber(x, y, output); - return output; - } - - /// Computes the element-wise number with the largest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MaxMagnitudeNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.MaxMagnitudeNumber); - } - #endregion - - #region MaxNumber - /// Searches for the largest number in the specified tensor. - /// The input .. - public static T MaxNumber(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.MaxNumber); - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MaxNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - MaxNumber(x, y, output); - return output; - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MaxNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.MaxNumber); - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MaxNumber(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - MaxNumber(x, y, output); - return output; - } - - /// Computes the element-wise maximum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MaxNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.MaxNumber); - } - #endregion - - #region Min - /// Searches for the smallest number in the specified tensor. - /// The input . - public static T Min(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.Min); - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor Min(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Min(x, y, output); - return output; - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan Min(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Min); - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor Min(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - Min(x, y, output); - return output; - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan Min(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Min); - } - #endregion - - #region MinMagnitude - /// Searches for the number with the smallest magnitude in the specified tensor. - /// The input . - public static T MinMagnitude(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.MinMagnitude); - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MinMagnitude(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - MinMagnitude(x, y, output); - return output; - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MinMagnitude(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.MinMagnitude); - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MinMagnitude(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - MinMagnitude(x, y, output); - return output; - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MinMagnitude(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.MinMagnitude); - } - #endregion - - #region MinMagnitudeNumber - /// Searches for the number with the smallest magnitude in the specified tensor. - /// The input .. - public static T MinMagnitudeNumber(scoped in ReadOnlyTensorSpan x) - where T : INumberBase - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.MinMagnitudeNumber); - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MinMagnitudeNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - MinMagnitudeNumber(x, y, output); - return output; - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MinMagnitudeNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.MinMagnitudeNumber); - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MinMagnitudeNumber(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - MinMagnitudeNumber(x, y, output); - return output; - } - - /// Computes the element-wise number with the smallest magnitude in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MinMagnitudeNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.MinMagnitudeNumber); - } - #endregion - - #region MinNumber - /// Searches for the smallest number in the specified tensor. - /// The input .. - public static T MinNumber(scoped in ReadOnlyTensorSpan x) - where T : INumber - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.MinNumber); - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MinNumber(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : INumber - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - MinNumber(x, y, output); - return output; - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MinNumber(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.MinNumber); - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - public static Tensor MinNumber(in ReadOnlyTensorSpan x, T y) - where T : INumber - { - Tensor output = Tensor.Create(x.Lengths); - MinNumber(x, y, output); - return output; - } - - /// Computes the element-wise minimum of the numbers in the specified tensors. - /// The first tensor, represented as a span. - /// The second tensor, represented as a span. - /// The destination tensor, represented as a span. - public static ref readonly TensorSpan MinNumber(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : INumber - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.MinNumber); - } - #endregion - - #region Multiply - /// - /// Multiplies each element of with and returns a new with the result. - /// - /// Input - /// value to multiply by. - public static Tensor Multiply(in ReadOnlyTensorSpan x, T y) - where T : IMultiplyOperators, IMultiplicativeIdentity - { - Tensor output = Tensor.Create(x.Lengths); - Multiply((ReadOnlyTensorSpan)x, y, output); - return output; - } - - /// - /// Multiplies each element of with and returns a new with the result. - /// If the shapes are not the same they are broadcast to the smallest compatible shape. - /// - /// Left for multiplication. - /// Right for multiplication. - public static Tensor Multiply(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IMultiplyOperators, IMultiplicativeIdentity - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Multiply((ReadOnlyTensorSpan)x, y, output); - return output; - } - - /// - /// Multiplies each element of with and returns a new with the result. - /// - /// Input - /// value to multiply by. - /// - public static ref readonly TensorSpan Multiply(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IMultiplyOperators, IMultiplicativeIdentity - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - TensorPrimitives.Multiply(span, y, ospan); - return ref destination; - } - - /// - /// Multiplies each element of with and returns a new with the result. - /// If the shapes are not the same they are broadcast to the smallest compatible shape. - /// - /// Left for multiplication. - /// Right for multiplication. - /// - public static ref readonly TensorSpan Multiply(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IMultiplyOperators, IMultiplicativeIdentity - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Multiply); - } - #endregion - - #region Negate - /// Computes the element-wise negation of each number in the specified tensor. - /// The - public static Tensor Negate(in ReadOnlyTensorSpan x) - where T : IUnaryNegationOperators - { - Tensor output = Tensor.Create(x.Lengths); - Negate(x, output); - return output; - } - - /// Computes the element-wise negation of each number in the specified tensor. - /// The - /// - public static ref readonly TensorSpan Negate(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IUnaryNegationOperators - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Negate); - } - #endregion - - #region Norm - /// - /// Takes the norm of the and returns the result. - /// - /// The to take the norm of. - public static T Norm(scoped in ReadOnlyTensorSpan x) - where T : IRootFunctions - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.Norm); - } - #endregion - - #region OnesComplement - /// Computes the element-wise one's complement of numbers in the specified tensor. - /// The - public static Tensor OnesComplement(in ReadOnlyTensorSpan x) - where T : IBitwiseOperators - { - Tensor output = Tensor.Create(x.Lengths); - OnesComplement(x, output); - return output; - } - - /// Computes the element-wise one's complement of numbers in the specified tensor. - /// The - /// - public static ref readonly TensorSpan OnesComplement(scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IBitwiseOperators - { - return ref TensorPrimitivesHelperSpanInSpanOut(y, destination, TensorPrimitives.OnesComplement); - } - #endregion - - #region PopCount - /// Computes the element-wise population count of numbers in the specified tensor. - /// The - public static Tensor PopCount(in ReadOnlyTensorSpan x) - where T : IBinaryInteger - { - Tensor output = Tensor.Create(x.Lengths); - PopCount(x, output); - return output; - } - - /// Computes the element-wise population count of numbers in the specified tensor. - /// The - /// - public static ref readonly TensorSpan PopCount(scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IBinaryInteger - { - return ref TensorPrimitivesHelperSpanInSpanOut(y, destination, TensorPrimitives.PopCount); - } - #endregion - - #region Pow - /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. - /// The input . - /// The second input - public static Tensor Pow(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IPowerFunctions - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Pow(x, y, output); - return output; - } - - /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. - /// The input . - /// The second input - /// - public static ref readonly TensorSpan Pow(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IPowerFunctions - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Pow); - } - - /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. - /// The input . - /// The second input - public static Tensor Pow(in ReadOnlyTensorSpan x, T y) - where T : IPowerFunctions - { - Tensor output = Tensor.Create(x.Lengths); - - Pow(x, y, output); - return output; - } - - /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. - /// The input . - /// The second input - /// - public static ref readonly TensorSpan Pow(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IPowerFunctions - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Pow); - } - - /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. - /// The input . - /// The second input - public static Tensor Pow(T x, in ReadOnlyTensorSpan y) - where T : IPowerFunctions - { - Tensor output = Tensor.Create(y.Lengths); - - Pow(x, y, output); - return output; - } - - /// Computes the element-wise power of a number in a specified tensor raised to a number in another specified tensors. - /// The input . - /// The second input - /// - public static ref readonly TensorSpan Pow(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IPowerFunctions - { - return ref TensorPrimitivesHelperTInSpanInSpanOut(x, y, destination, TensorPrimitives.Pow); - } - #endregion - - #region Product - /// Computes the product of all elements in the specified non-empty tensor of numbers. - /// The input . - public static T Product(scoped in ReadOnlyTensorSpan x) - where T : IMultiplicativeIdentity, IMultiplyOperators - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.Product); - } - #endregion - - #region RadiansToDegrees - /// Computes the element-wise conversion of each number of radians in the specified tensor to degrees. - /// The input . - public static Tensor RadiansToDegrees(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - RadiansToDegrees(x, output); - return output; - } - - /// Computes the element-wise conversion of each number of radians in the specified tensor to degrees. - /// The input . - /// - public static ref readonly TensorSpan RadiansToDegrees(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.RadiansToDegrees); - } - #endregion - - #region Reciprocal - /// Computes the element-wise reciprocal of numbers in the specified tensor. - /// The input . - public static Tensor Reciprocal(in ReadOnlyTensorSpan x) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Reciprocal(x, output); - return output; - } - - /// Computes the element-wise reciprocal of numbers in the specified tensor. - /// The input . - /// - public static ref readonly TensorSpan Reciprocal(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IFloatingPoint - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Reciprocal); - } - #endregion - - #region RootN - /// Computes the element-wise n-th root of the values in the specified tensor. - /// The tensor, represented as a span. - /// The degree of the root to be computed, represented as a scalar. - public static Tensor RootN(in ReadOnlyTensorSpan x, int n) - where T : IRootFunctions - { - Tensor output = Tensor.Create(x.Lengths); - RootN(x, n, output); - return output; - } - - /// Computes the element-wise n-th root of the values in the specified tensor. - /// The tensor, represented as a span. - /// The destination tensor, represented as a span. - /// The degree of the root to be computed, represented as a scalar. - public static ref readonly TensorSpan RootN(scoped in ReadOnlyTensorSpan x, int n, in TensorSpan destination) - where T : IRootFunctions - { - if (destination._shape._memoryLength < x._shape._memoryLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - TensorPrimitives.RootN(span, n, ospan); - return ref destination; - } - #endregion - - #region RotateLeft - /// Computes the element-wise rotation left of numbers in the specified tensor by the specified rotation amount. - /// The tensor - /// The number of bits to rotate, represented as a scalar. - /// Destination is too short. - public static Tensor RotateLeft(in ReadOnlyTensorSpan x, int rotateAmount) - where T : IBinaryInteger - { - Tensor output = Tensor.Create(x.Lengths); - RotateLeft(x, rotateAmount, output); - return output; - } - - /// Computes the element-wise rotation left of numbers in the specified tensor by the specified rotation amount. - /// The tensor - /// The number of bits to rotate, represented as a scalar. - /// - /// Destination is too short. - public static ref readonly TensorSpan RotateLeft(scoped in ReadOnlyTensorSpan x, int rotateAmount, in TensorSpan destination) - where T : IBinaryInteger - { - if (destination._shape._memoryLength < x._shape._memoryLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - TensorPrimitives.RotateLeft(span, rotateAmount, ospan); - return ref destination; - } - #endregion - - #region RotateRight - /// Computes the element-wise rotation right of numbers in the specified tensor by the specified rotation amount. - /// The tensor - /// The number of bits to rotate, represented as a scalar. - /// Destination is too short. - public static Tensor RotateRight(in ReadOnlyTensorSpan x, int rotateAmount) - where T : IBinaryInteger - { - Tensor output = Tensor.Create(x.Lengths); - RotateRight(x, rotateAmount, output); - return output; - } - - /// Computes the element-wise rotation right of numbers in the specified tensor by the specified rotation amount. - /// The tensor - /// The number of bits to rotate, represented as a scalar. - /// - /// Destination is too short. - public static ref readonly TensorSpan RotateRight(scoped in ReadOnlyTensorSpan x, int rotateAmount, in TensorSpan destination) - where T : IBinaryInteger - { - if (destination._shape._memoryLength < x._shape._memoryLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - TensorPrimitives.RotateRight(span, rotateAmount, ospan); - return ref destination; - } - #endregion - - #region Round - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - public static Tensor Round(in ReadOnlyTensorSpan x) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Round(x, output); - return output; - } - - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - /// - public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IFloatingPoint - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Round); - } - - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - /// - /// - public static Tensor Round(in ReadOnlyTensorSpan x, int digits, MidpointRounding mode) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Round(x, digits, mode, output); - return output; - } - - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - /// - /// - /// - public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, int digits, MidpointRounding mode, in TensorSpan destination) - where T : IFloatingPoint - { - if (destination._shape._memoryLength < x._shape._memoryLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - TensorPrimitives.Round(span, digits, mode, ospan); - return ref destination; - } - - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - /// - public static Tensor Round(in ReadOnlyTensorSpan x, int digits) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Round(x, digits, output); - return output; - } - - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - /// - /// - public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, int digits, in TensorSpan destination) - where T : IFloatingPoint - { - if (destination._shape._memoryLength < x._shape._memoryLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - TensorPrimitives.Round(span, digits, ospan); - return ref destination; - } - - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - /// - public static Tensor Round(in ReadOnlyTensorSpan x, MidpointRounding mode) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Round(x, mode, output); - return output; - } - - /// Computes the element-wise rounding of the numbers in the specified tensor - /// The input . - /// - /// - public static ref readonly TensorSpan Round(scoped in ReadOnlyTensorSpan x, MidpointRounding mode, in TensorSpan destination) - where T : IFloatingPoint - { - if (destination._shape._memoryLength < x._shape._memoryLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref x._reference, (int)x._shape._memoryLength); - Span ospan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - TensorPrimitives.Round(span, mode, ospan); - return ref destination; - } - #endregion - - #region Sigmoid - /// Computes the element-wise sigmoid function on the specified non-empty tensor of numbers. - /// The input . - public static Tensor Sigmoid(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Sigmoid(x, output); - return output; - } - - /// Computes the element-wise sigmoid function on the specified non-empty tensor of numbers. - /// The input . - /// - public static ref readonly TensorSpan Sigmoid(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Sigmoid); - } - #endregion - - #region Sin - /// - /// Takes the sin of each element of the and returns a new with the result. - /// - /// The to take the sin of. - public static Tensor Sin(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Sin(x, output); - return output; - } - - /// - /// Takes the sin of each element of the and returns a new with the result. - /// - /// The to take the sin of. - /// - public static ref readonly TensorSpan Sin(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Sin); - } - #endregion - - #region Sinh - /// Computes the element-wise hyperbolic sine of each radian angle in the specified tensor. - /// The to take the sin of. - public static Tensor Sinh(in ReadOnlyTensorSpan x) - where T : IHyperbolicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Sinh(x, output); - return output; - } - - /// Computes the element-wise hyperbolic sine of each radian angle in the specified tensor. - /// The to take the sin of. - /// - public static ref readonly TensorSpan Sinh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IHyperbolicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Sinh); - } - #endregion - - #region SinPi - /// Computes the element-wise sine of the value in the specified tensor that has been multiplied by Pi. - /// The to take the sin of. - public static Tensor SinPi(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - SinPi(x, output); - return output; - } - - /// Computes the element-wise sine of the value in the specified tensor that has been multiplied by Pi. - /// The to take the sin of. - /// - public static ref readonly TensorSpan SinPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.SinPi); - } - #endregion - - #region SoftMax - /// Computes the softmax function over the specified non-empty tensor of numbers. - /// The to take the sin of. - public static Tensor SoftMax(in ReadOnlyTensorSpan x) - where T : IExponentialFunctions - { - Tensor output = Tensor.Create(x.Lengths); - SoftMax(x, output); - return output; - } - - /// Computes the softmax function over the specified non-empty tensor of numbers. - /// The to take the sin of. - /// - public static ref readonly TensorSpan SoftMax(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IExponentialFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.SoftMax); - } - #endregion - - #region Sqrt - /// - /// Takes the square root of each element of the and returns a new with the result. - /// - /// The to take the square root of. - public static Tensor Sqrt(in ReadOnlyTensorSpan x) - where T : IRootFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Sqrt(x, output); - return output; - } - - /// - /// Takes the square root of each element of the and returns a new with the result. - /// - /// The to take the square root of. - /// - public static ref readonly TensorSpan Sqrt(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IRootFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Sqrt); - } - #endregion - - #region StdDev - /// - /// Returns the standard deviation of the elements in the tensor. - /// - /// The to take the standard deviation of. - /// representing the standard deviation. - public static T StdDev(in ReadOnlyTensorSpan x) - where T : IRootFunctions - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.StdDev); - } - #endregion - - #region Subtract - /// - /// Subtracts from each element of and returns a new with the result. - /// - /// The . - /// The to subtract. - public static Tensor Subtract(in ReadOnlyTensorSpan x, T y) - where T : ISubtractionOperators - { - Tensor output = Create(x.Lengths); - Subtract(x, y, output); - return output; - } - - /// - /// Subtracts each element of from and returns a new with the result. - /// - /// The to be subtracted from. - /// The of values to subtract. - public static Tensor Subtract(T x, in ReadOnlyTensorSpan y) - where T : ISubtractionOperators - { - Tensor output = Create(y.Lengths); - Subtract(x, y, output); - return output; - } - - /// - /// Subtracts each element of from and returns a new with the result. - /// - /// The with values to be subtracted from. - /// The with values to subtract. - public static Tensor Subtract(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : ISubtractionOperators - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Subtract(x, y, output); - return output; - } - - /// - /// Subtracts from each element of and returns a new with the result. - /// - /// The with values to be subtracted from. - /// The value to subtract. - /// - public static ref readonly TensorSpan Subtract(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : ISubtractionOperators - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Subtract); - } - - /// - /// Subtracts each element of from and returns a new with the result. - /// - /// The value to be subtracted from. - /// The values to subtract. - /// - public static ref readonly TensorSpan Subtract(T x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : ISubtractionOperators - { - return ref TensorPrimitivesHelperTInSpanInSpanOut(x, y, destination, TensorPrimitives.Subtract); - } - - /// - /// Subtracts each element of from and returns a new with the result. - /// - /// The of values to be subtracted from. - /// The of values to subtract. - /// - public static ref readonly TensorSpan Subtract(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : ISubtractionOperators - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Subtract); - } - #endregion - - #region Sum - /// - /// Sums the elements of the specified tensor. - /// - /// Tensor to sum - /// - public static T Sum(scoped in ReadOnlyTensorSpan x) - where T : IAdditionOperators, IAdditiveIdentity - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.Sum); - } - #endregion - - #region SumOfSquares - /// - /// Sums the squared elements of the specified tensor. - /// - /// Tensor to sum squares of - /// - internal static T SumOfSquares(scoped in ReadOnlyTensorSpan x) - where T : IAdditionOperators, IAdditiveIdentity, IMultiplyOperators - { - return TensorPrimitivesHelperSpanInTOut(x, TensorPrimitives.SumOfSquares); - } - #endregion - - #region Tan - /// Computes the element-wise tangent of the value in the specified tensor. - /// The to take the sin of. - public static Tensor Tan(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Tan(x, output); - return output; - } - - /// Computes the element-wise tangent of the value in the specified tensor. - /// The to take the sin of. - /// - public static ref readonly TensorSpan Tan(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Tan); - } - #endregion - - #region Tanh - /// Computes the element-wise hyperbolic tangent of each radian angle in the specified tensor. - /// The to take the sin of. - public static Tensor Tanh(in ReadOnlyTensorSpan x) - where T : IHyperbolicFunctions - { - Tensor output = Tensor.Create(x.Lengths); - Tanh(x, output); - return output; - } - - /// Computes the element-wise hyperbolic tangent of each radian angle in the specified tensor. - /// The to take the sin of. - /// - public static ref readonly TensorSpan Tanh(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IHyperbolicFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Tanh); - } - #endregion - - #region TanPi - /// Computes the element-wise tangent of the value in the specified tensor that has been multiplied by Pi. - /// The to take the sin of. - public static Tensor TanPi(in ReadOnlyTensorSpan x) - where T : ITrigonometricFunctions - { - Tensor output = Tensor.Create(x.Lengths); - TanPi(x, output); - return output; - } - - /// Computes the element-wise tangent of the value in the specified tensor that has been multiplied by Pi. - /// The to take the sin of. - /// - public static ref readonly TensorSpan TanPi(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : ITrigonometricFunctions - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.TanPi); - } - #endregion - - #region TrailingZeroCount - /// Computes the element-wise trailing zero count of numbers in the specified tensor. - /// The input . - public static Tensor TrailingZeroCount(in ReadOnlyTensorSpan x) - where T : IBinaryInteger - { - Tensor output = Tensor.Create(x.Lengths); - TrailingZeroCount(x, output); - return output; - } - - /// Computes the element-wise trailing zero count of numbers in the specified tensor. - /// The input . - /// - public static ref readonly TensorSpan TrailingZeroCount(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IBinaryInteger - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.TrailingZeroCount); - } - #endregion - - #region Truncate - /// Computes the element-wise truncation of numbers in the specified tensor. - /// The input . - public static Tensor Truncate(in ReadOnlyTensorSpan x) - where T : IFloatingPoint - { - Tensor output = Tensor.Create(x.Lengths); - Truncate(x, output); - return output; - } - - /// Computes the element-wise truncation of numbers in the specified tensor. - /// The input . - /// - public static ref readonly TensorSpan Truncate(scoped in ReadOnlyTensorSpan x, in TensorSpan destination) - where T : IFloatingPoint - { - return ref TensorPrimitivesHelperSpanInSpanOut(x, destination, TensorPrimitives.Truncate); - } - #endregion - - #region Xor - /// Computes the element-wise XOR of numbers in the specified tensors. - /// The left . - /// The right . - public static Tensor Xor(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) - where T : IBitwiseOperators - { - Tensor output; - if (x.Lengths.SequenceEqual(y.Lengths)) - { - output = Tensor.Create(x.Lengths); - } - else - { - output = Tensor.Create(GetSmallestBroadcastableLengths(x.Lengths, y.Lengths)); - } - - Xor(x, y, output); - return output; - } - - /// Computes the element-wise XOR of numbers in the specified tensors. - /// The left . - /// The right . - /// - public static ref readonly TensorSpan Xor(scoped in ReadOnlyTensorSpan x, scoped in ReadOnlyTensorSpan y, in TensorSpan destination) - where T : IBitwiseOperators - { - return ref TensorPrimitivesHelperTwoSpanInSpanOut(x, y, destination, TensorPrimitives.Xor); - } - - /// - /// Computes the element-wise Xor of the two input and returns a new with the result. - /// - /// The left . - /// The second value. - public static Tensor Xor(in ReadOnlyTensorSpan x, T y) - where T : IBitwiseOperators - { - Tensor output = Tensor.Create(x.Lengths); - Xor(x, y, output); - return output; - } - - /// - /// Computes the element-wise Xor of the two input and returns a new with the result. - /// - /// The left . - /// The second value. - /// - public static ref readonly TensorSpan Xor(scoped in ReadOnlyTensorSpan x, T y, in TensorSpan destination) - where T : IBitwiseOperators - { - return ref TensorPrimitivesHelperSpanInTInSpanOut(x, y, destination, TensorPrimitives.Xor); - } - #endregion - - /// - /// Gets the smallest broadcastable lengths for two shapes. - /// - /// The first shape to broadcast. - /// The second shape to broadcast. - /// The smallest lengths these shapes can be broadcast to. - /// The lengths of and are not broadcast compatible. - public static nint[] GetSmallestBroadcastableLengths(ReadOnlySpan shape1, ReadOnlySpan shape2) - { - if (!TensorHelpers.IsBroadcastableTo(shape1, shape2)) - ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible(); - - nint[] intermediateShape = TensorHelpers.GetIntermediateShape(shape1, shape2.Length); - for (int i = 1; i <= shape1.Length; i++) - { - intermediateShape[^i] = Math.Max(intermediateShape[^i], shape1[^i]); - } - for (int i = 1; i <= shape2.Length; i++) - { - intermediateShape[^i] = Math.Max(intermediateShape[^i], shape2[^i]); - } - - return intermediateShape; - } - - #region TensorPrimitivesHelpers - private delegate void PerformCalculationSpanInSpanOut(ReadOnlySpan input, Span output); - - private delegate void PerformCalculationSpanInTInSpanOut(ReadOnlySpan input, T value, Span output); - - private delegate void PerformCalculationTInSpanInSpanOut(T value, ReadOnlySpan input, Span output); - - private delegate void PerformCalculationTwoSpanInSpanOut(ReadOnlySpan input, ReadOnlySpan inputTwo, Span output); - - private delegate T PerformCalculationTwoSpanInTOut(ReadOnlySpan input, ReadOnlySpan inputTwo); - - private delegate T PerformCalculationSpanInTOut(ReadOnlySpan input); - - private static T TensorPrimitivesHelperSpanInTOut(scoped in ReadOnlyTensorSpan input, PerformCalculationSpanInTOut performCalculation) - { - if (TensorHelpers.IsContiguousAndDense(input)) - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref input._reference, (int)input._shape.FlattenedLength); - return performCalculation(span); - } - // Flattening needs to happen - else - { - // TODO: Can optimize this to not need to realize the broadcasts - // That will need to be done on a per method basis. - nint flattenedLength = input.FlattenedLength; - T[] flattened = new T[flattenedLength]; - input.FlattenTo(flattened); - return performCalculation(flattened); - } - } - - private static T TensorPrimitivesHelperTwoSpanInTOut(scoped in ReadOnlyTensorSpan left, scoped in ReadOnlyTensorSpan right, PerformCalculationTwoSpanInTOut performCalculation) - { - // If sizes are the same. - if (TensorHelpers.IsContiguousAndDense(left) && TensorHelpers.IsContiguousAndDense(right) && TensorHelpers.AreLengthsTheSame(left, right)) - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref left._reference, (int)left._shape.FlattenedLength); - ReadOnlySpan rspan = MemoryMarshal.CreateSpan(ref right._reference, (int)right._shape.FlattenedLength); - return performCalculation(span, rspan); - } - // Broadcasting needs to happen. - else - { - // Have a couple different possible cases here. - // 1 - Both tensors have row contiguous memory (i.e. a 1x5 being broadcast to a 5x5) - // 2 - One tensor has row contiguous memory and the right has column contiguous memory (i.e. a 1x5 and a 5x1) - // Because we are returning a single T though we need to actual realize the broadcasts at this point to perform the calculations. - - // TODO: Can optimize this to not need to realize the broadcasts - // That will need to be done on a per method basis. - nint[] newLengths = Tensor.GetSmallestBroadcastableLengths(left.Lengths, right.Lengths); - nint newLength = TensorSpanHelpers.CalculateTotalLength(newLengths); - TensorSpan broadcastedLeft = new TensorSpan(new T[newLength], newLengths, ReadOnlySpan.Empty); - TensorSpan broadcastedRight = new TensorSpan(new T[newLength], newLengths, ReadOnlySpan.Empty); - BroadcastTo(left, broadcastedLeft); - BroadcastTo(right, broadcastedRight); - - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref broadcastedLeft._reference, (int)broadcastedLeft.FlattenedLength); - ReadOnlySpan rspan = MemoryMarshal.CreateSpan(ref broadcastedRight._reference, (int)broadcastedRight.FlattenedLength); - return performCalculation(span, rspan); - } - } - - private static ref readonly TensorSpan TensorPrimitivesHelperSpanInSpanOut(scoped in ReadOnlyTensorSpan input, in TensorSpan destination, PerformCalculationSpanInSpanOut performCalculation) - { - // Make sure destination has enough memory - if (destination._shape._memoryLength < input._shape.FlattenedLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - // Make sure destination shape works with input shape - TensorSpan slicedDestination = destination.Slice(input._shape.Lengths); - - Span destinationSpan; - ReadOnlySpan inputSpan; - - // Memory is contiguous for both input and destination - if (TensorHelpers.IsContiguousAndDense(input) && TensorHelpers.IsContiguousAndDense(slicedDestination)) - { - inputSpan = MemoryMarshal.CreateSpan(ref input._reference, (int)input._shape.FlattenedLength); - destinationSpan = MemoryMarshal.CreateSpan(ref slicedDestination._reference, (int)slicedDestination._shape.FlattenedLength); - performCalculation(inputSpan, destinationSpan); - } - else - { - scoped Span curIndex; - nint[]? curIndexArray; - if (input.Lengths.Length > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(input.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, input.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[input.Lengths.Length]; - } - curIndex.Clear(); - - int copiedValues = 0; - nint rowLength = input.Lengths[^1]; - - while (copiedValues < slicedDestination.FlattenedLength) - { - inputSpan = MemoryMarshal.CreateReadOnlySpan(in input[curIndex], (int)rowLength); - destinationSpan = MemoryMarshal.CreateSpan(ref slicedDestination[curIndex], (int)rowLength); - performCalculation(inputSpan, destinationSpan); - copiedValues += (int)rowLength; - TensorSpanHelpers.AdjustIndexes(input.Rank - 2, 1, curIndex, input.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - } - - return ref destination; - } - - private static ref readonly TensorSpan TensorPrimitivesHelperSpanInTInSpanOut(scoped in ReadOnlyTensorSpan input, T value, in TensorSpan destination, PerformCalculationSpanInTInSpanOut performCalculation) - { - if (destination._shape._memoryLength < input._shape.FlattenedLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - // Make sure destination shape works with input shape - TensorSpan slicedDestination = destination.Slice(input._shape.Lengths); - - ReadOnlySpan inputSpan; - Span destinationSpan; - - if (TensorHelpers.IsContiguousAndDense(input) && TensorHelpers.IsContiguousAndDense(slicedDestination)) - { - inputSpan = MemoryMarshal.CreateSpan(ref input._reference, (int)input.FlattenedLength); - destinationSpan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination.FlattenedLength); - performCalculation(inputSpan, value, destinationSpan); - } - else - { - scoped Span curIndex; - nint[]? curIndexArray; - if (input.Lengths.Length > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(input.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, input.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[input.Lengths.Length]; - } - curIndex.Clear(); - - int copiedValues = 0; - nint rowLength = input.Lengths[^1]; - - while (copiedValues < slicedDestination.FlattenedLength) - { - inputSpan = MemoryMarshal.CreateReadOnlySpan(in input[curIndex], (int)rowLength); - destinationSpan = MemoryMarshal.CreateSpan(ref slicedDestination[curIndex], (int)rowLength); - performCalculation(inputSpan, value, destinationSpan); - copiedValues += (int)rowLength; - TensorSpanHelpers.AdjustIndexes(input.Rank - 2, 1, curIndex, input.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - } - - return ref destination; - } - - private static ref readonly TensorSpan TensorPrimitivesHelperTInSpanInSpanOut(T value, scoped in ReadOnlyTensorSpan input, in TensorSpan destination, PerformCalculationTInSpanInSpanOut performCalculation) - { - if (destination._shape._memoryLength < input._shape.FlattenedLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - - // Make sure destination shape works with input shape - TensorSpan slicedDestination = destination.Slice(input._shape.Lengths); - - ReadOnlySpan inputSpan; - Span destinationSpan; - - if (TensorHelpers.IsContiguousAndDense(input) && TensorHelpers.IsContiguousAndDense(slicedDestination)) - { - inputSpan = MemoryMarshal.CreateSpan(ref input._reference, (int)input._shape._memoryLength); - destinationSpan = MemoryMarshal.CreateSpan(ref destination._reference, (int)destination._shape._memoryLength); - performCalculation(value, inputSpan, destinationSpan); - } - else - { - scoped Span curIndex; - nint[]? curIndexArray; - if (input.Lengths.Length > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(input.Lengths.Length); - curIndex = curIndexArray.AsSpan(0, input.Rank); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[input.Lengths.Length]; - } - curIndex.Clear(); - - int copiedValues = 0; - nint rowLength = input.Lengths[^1]; - - while (copiedValues < slicedDestination.FlattenedLength) - { - inputSpan = MemoryMarshal.CreateReadOnlySpan(in input[curIndex], (int)rowLength); - destinationSpan = MemoryMarshal.CreateSpan(ref slicedDestination[curIndex], (int)rowLength); - performCalculation(value, inputSpan, destinationSpan); - copiedValues += (int)rowLength; - TensorSpanHelpers.AdjustIndexes(input.Rank - 2, 1, curIndex, input.Lengths); - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - } - - return ref destination; - } - - private static ref readonly TensorSpan TensorPrimitivesHelperTwoSpanInSpanOut(scoped in ReadOnlyTensorSpan left, scoped in ReadOnlyTensorSpan right, in TensorSpan destination, PerformCalculationTwoSpanInSpanOut performCalculation) - { - nint[] newSize = Tensor.GetSmallestBroadcastableLengths(left.Lengths, right.Lengths); - - TensorSpan slicedDestination = destination.Slice(newSize); - - // If sizes are the same and memory is contiguous for all tensors - if (TensorHelpers.AreLengthsTheSame(left, right) && TensorHelpers.IsUnderlyingStorageSameSize(left, right) && TensorHelpers.IsContiguousAndDense(left) - && TensorHelpers.IsContiguousAndDense(right) && TensorHelpers.IsContiguousAndDense(slicedDestination)) - { - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref left._reference, left._shape._memoryLength <= left.FlattenedLength ? (int)left._shape._memoryLength : (int)left.FlattenedLength); - ReadOnlySpan rspan = MemoryMarshal.CreateSpan(ref right._reference, right._shape._memoryLength <= right.FlattenedLength ? (int)right._shape._memoryLength : (int)right.FlattenedLength); - Span ospan = MemoryMarshal.CreateSpan(ref slicedDestination._reference, (int)slicedDestination._shape._memoryLength); - performCalculation(span, rspan, ospan); - return ref destination; - } - // Broadcasting needs to happen. - else - { - // Have a couple different possible cases here. - // 1 - Both tensors have row contiguous memory (i.e. a 1x5 being broadcast to a 5x5) - // 2 - One tensor has row contiguous memory and the right has column contiguous memory (i.e. a 1x5 and a 5x1) - - ReadOnlyTensorSpan broadcastedLeft = Tensor.LazyBroadcast(left, newSize); - ReadOnlyTensorSpan broadcastedRight = Tensor.LazyBroadcast(right, newSize); - - nint rowLength = newSize[^1]; - Span ospan; - ReadOnlySpan ispan; - Span buffer = new T[rowLength]; - - scoped Span curIndex; - nint[]? curIndexArray; - if (newSize.Length > TensorShape.MaxInlineRank) - { - curIndexArray = ArrayPool.Shared.Rent(newSize.Length); - curIndex = curIndexArray.AsSpan(0, newSize.Length); - } - else - { - curIndexArray = null; - curIndex = stackalloc nint[newSize.Length]; - } - curIndex.Clear(); - - int outputOffset = 0; - // neither row contiguous - if (broadcastedLeft.Strides[^1] == 0 && broadcastedRight.Strides[^1] == 0) - { - Span buffer2 = new T[rowLength]; - - while (outputOffset < slicedDestination.FlattenedLength) - { - ospan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref slicedDestination._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, slicedDestination.Strides, slicedDestination.Lengths)), (int)rowLength); - buffer.Fill(broadcastedLeft[curIndex]); - buffer2.Fill(broadcastedRight[curIndex]); - performCalculation(buffer, buffer2, ospan); - outputOffset += (int)rowLength; - TensorSpanHelpers.AdjustIndexes(broadcastedLeft.Rank - 2, 1, curIndex, broadcastedLeft.Lengths); - } - } - // tensor not row contiguous - else if (broadcastedLeft.Strides[^1] == 0) - { - while (outputOffset < slicedDestination.FlattenedLength) - { - ospan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref slicedDestination._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, slicedDestination.Strides, slicedDestination.Lengths)), (int)rowLength); - buffer.Fill(broadcastedLeft[curIndex]); - ispan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref broadcastedRight._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, broadcastedRight.Strides, broadcastedRight.Lengths)), (int)rowLength); - performCalculation(buffer, ispan, ospan); - outputOffset += (int)rowLength; - TensorSpanHelpers.AdjustIndexes(broadcastedLeft.Rank - 2, 1, curIndex, broadcastedLeft.Lengths); - } - } - // right not row contiguous - else if (broadcastedRight.Strides[^1] == 0) - { - while (outputOffset < slicedDestination.FlattenedLength) - { - ospan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref slicedDestination._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, slicedDestination.Strides, slicedDestination.Lengths)), (int)rowLength); - buffer.Fill(broadcastedRight[curIndex]); - ispan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref broadcastedLeft._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, broadcastedLeft.Strides, broadcastedLeft.Lengths)), (int)rowLength); - performCalculation(ispan, buffer, ospan); - outputOffset += (int)rowLength; - TensorSpanHelpers.AdjustIndexes(broadcastedLeft.Rank - 2, 1, curIndex, broadcastedLeft.Lengths); - } - } - // both row contiguous - else - { - Span rspan; - while (outputOffset < slicedDestination.FlattenedLength) - { - ospan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref slicedDestination._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, slicedDestination.Strides, slicedDestination.Lengths)), (int)rowLength); - ispan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref broadcastedLeft._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, broadcastedLeft.Strides, broadcastedLeft.Lengths)), (int)rowLength); - rspan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref broadcastedRight._reference, TensorSpanHelpers.ComputeLinearIndex(curIndex, broadcastedRight.Strides, broadcastedRight.Lengths)), (int)rowLength); - performCalculation(ispan, rspan, ospan); - outputOffset += (int)rowLength; - TensorSpanHelpers.AdjustIndexes(broadcastedLeft.Rank - 2, 1, curIndex, broadcastedLeft.Lengths); - } - } - - if (curIndexArray != null) - ArrayPool.Shared.Return(curIndexArray); - } - return ref destination; - } - - #endregion - - #endregion - } -} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorHelpers.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorHelpers.cs deleted file mode 100644 index c368c65fd1c6d5..00000000000000 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorHelpers.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace System.Numerics.Tensors -{ - [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - internal static class TensorHelpers - { - /// - /// Counts the number of true elements in a boolean filter tensor so we know how much space we will need. - /// - /// - /// How many boolean values are true. - public static nint CountTrueElements(scoped in ReadOnlyTensorSpan filter) - { - Span filterSpan = MemoryMarshal.CreateSpan(ref filter._reference, (int)filter._shape._memoryLength); - nint count = 0; - for (int i = 0; i < filterSpan.Length; i++) - { - if (filterSpan[i]) - count++; - } - - return count; - } - - internal static bool IsBroadcastableTo(Tensor tensor1, Tensor tensor2) - where T : IEquatable, IEqualityOperators => IsBroadcastableTo(tensor1.Lengths, tensor2.Lengths); - - internal static bool IsBroadcastableTo(ReadOnlySpan lengths1, ReadOnlySpan lengths2) - { - int lengths1Index = lengths1.Length - 1; - int lengths2Index = lengths2.Length - 1; - - bool areCompatible = true; - - nint s1; - nint s2; - - if (lengths1.Length == 0 || lengths2.Length == 0) - return false; - - while (lengths1Index >= 0 || lengths2Index >= 0) - { - // if a dimension is missing in one of the shapes, it is considered to be 1 - if (lengths1Index < 0) - s1 = 1; - else - s1 = lengths1[lengths1Index--]; - - if (lengths2Index < 0) - s2 = 1; - else - s2 = lengths2[lengths2Index--]; - - if (s1 == s2 || (s1 == 1 && s2 > 1) || (s2 == 1 && s1 > 1)) { } - else - { - areCompatible = false; - break; - } - } - - return areCompatible; - } - - internal static nint[] GetIntermediateShape(ReadOnlySpan shape1, int shape2Length) - { - int shape1Index = shape1.Length - 1; - int newShapeIndex = Math.Max(shape1.Length, shape2Length) - 1; - nint[] newShape = new nint[Math.Max(shape1.Length, shape2Length)]; - - while (newShapeIndex >= 0) - { - // if a dimension is missing in one of the shapes, it is considered to be 1 - if (shape1Index < 0) - newShape[newShapeIndex--] = 1; - else - newShape[newShapeIndex--] = shape1[shape1Index--]; - } - - return newShape; - } - - internal static bool IsUnderlyingStorageSameSize(scoped in ReadOnlyTensorSpan tensor1, scoped in ReadOnlyTensorSpan tensor2) - => tensor1._shape._memoryLength == tensor2._shape._memoryLength; - - internal static bool IsUnderlyingStorageSameSize(Tensor tensor1, Tensor tensor2) - => tensor1._values.Length == tensor2._values.Length; - - internal static bool AreLengthsTheSame(scoped in ReadOnlyTensorSpan tensor1, scoped in ReadOnlyTensorSpan tensor2) - => tensor1.Lengths.SequenceEqual(tensor2.Lengths); - - internal static bool AreLengthsTheSame(ReadOnlySpan lengths1, ReadOnlySpan lengths2) - => lengths1.SequenceEqual(lengths2); - - internal static bool IsContiguousAndDense(scoped in ReadOnlyTensorSpan tensor) - { - // Right most dimension must be 1 for a dense tensor. - if (tensor._shape.Strides[^1] != 1) - return false; - - // For other dimensions, the stride must be equal to the product of the dimensions to the right. - for (int i = tensor._shape._rank - 2; i >= 0; i--) - { - if (tensor._shape.Strides[i] != TensorPrimitives.Product(tensor.Lengths.Slice(i + 1, tensor.Lengths.Length - i - 1))) - return false; - } - return true; - } - - internal static void PermuteIndices(Span indices, Span permutedIndices, ReadOnlySpan permutation) - { - for (int i = 0; i < indices.Length; i++) - { - permutedIndices[i] = indices[permutation[i]]; - } - } - } -} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorOperation.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorOperation.cs new file mode 100644 index 00000000000000..d372602f2fa429 --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorOperation.cs @@ -0,0 +1,2615 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System.Numerics.Tensors +{ + internal static class TensorOperation + { + public static void Invoke(in TensorSpan x) + where TOperation : TensorOperation.IOperation + { + scoped Span indexes = RentedBuffer.Create(x.Rank, x.Strides, out nint linearOffset, out RentedBuffer rentedBuffer); + + for (nint i = 0; i < x.FlattenedLength; i++) + { + linearOffset = x._shape.AdjustToNextIndex(x._shape, linearOffset, indexes); + TOperation.Invoke( + ref Unsafe.Add(ref x._reference, linearOffset) + ); + } + + rentedBuffer.Dispose(); + } + + public static bool Invoke(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Tensor + { + bool result = false; + + ref readonly TensorShape destinationShape = ref ((x._shape.FlattenedLength > y._shape.FlattenedLength) ? ref x._shape : ref y._shape); + scoped Span xIndexes = RentedBuffer.Create(destinationShape.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + scoped Span yIndexes = RentedBuffer.Create(destinationShape.Rank, y.Strides, out nint yLinearOffset, out RentedBuffer yRentedBuffer); + + for (nint i = 0; i < destinationShape.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(destinationShape, xLinearOffset, xIndexes); + yLinearOffset = y._shape.AdjustToNextIndex(destinationShape, yLinearOffset, yIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + in Unsafe.Add(ref y._reference, yLinearOffset), + ref result + ); + + if (!result) + { + break; + } + } + + xRentedBuffer.Dispose(); + yRentedBuffer.Dispose(); + + return result; + } + + public static bool Invoke(in ReadOnlyTensorSpan x, TArg y) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Scalar + { + bool result = false; + + scoped Span xIndexes = RentedBuffer.Create(x.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + + for (nint i = 0; i < x.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(x._shape, xLinearOffset, xIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + y, + ref result + ); + + if (!result) + { + return false; + } + } + + xRentedBuffer.Dispose(); + + return result; + } + + public static void Invoke(in TensorSpan destination, TArg scalar) + where TOperation : TensorOperation.IUnaryOperation_Scalar + { + scoped Span indexes = RentedBuffer.Create(destination.Rank, destination.Strides, out nint linearOffset, out RentedBuffer rentedBuffer); + + for (nint i = 0; i < destination.FlattenedLength; i++) + { + linearOffset = destination._shape.AdjustToNextIndex(destination._shape, linearOffset, indexes); + TOperation.Invoke( + ref Unsafe.Add(ref destination._reference, linearOffset), + scalar + ); + } + + rentedBuffer.Dispose(); + } + + public static void Invoke(in ReadOnlyTensorSpan x, in TensorSpan destination) + where TOperation : TensorOperation.IUnaryOperation_Tensor + { + scoped Span xIndexes = RentedBuffer.Create(destination.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + scoped Span destinationIndexes = RentedBuffer.Create(destination.Rank, destination.Strides, out nint destinationLinearOffset, out RentedBuffer destinationRentedBuffer); + + for (nint i = 0; i < destination.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(destination._shape, xLinearOffset, xIndexes); + destinationLinearOffset = destination._shape.AdjustToNextIndex(destination._shape, destinationLinearOffset, destinationIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + ref Unsafe.Add(ref destination._reference, destinationLinearOffset) + ); + } + + xRentedBuffer.Dispose(); + destinationRentedBuffer.Dispose(); + } + + public static void ReverseInvoke(in ReadOnlyTensorSpan x, in TensorSpan destination) + where TOperation : TensorOperation.IUnaryOperation_Tensor + { + scoped Span xIndexes = RentedBuffer.Create(destination.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + scoped Span destinationIndexes = RentedBuffer.Create(destination.Rank, destination.Strides, out nint _, out RentedBuffer destinationRentedBuffer); + + destinationIndexes[0] = destination.Lengths[0]; + for (int i = 1; i < destinationIndexes.Length; i++) + { + destinationIndexes[i] = destination.Lengths[i] - 1; + } + nint destinationLinearOffset = destination._shape.LinearLength; + + for (nint i = 0; i < destination.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(destination._shape, xLinearOffset, xIndexes); + destinationLinearOffset = destination._shape.AdjustToPreviousIndex(destination._shape, destinationLinearOffset, destinationIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + ref Unsafe.Add(ref destination._reference, destinationLinearOffset) + ); + } + + xRentedBuffer.Dispose(); + destinationRentedBuffer.Dispose(); + } + + // For copyto/flattento + public static void Invoke(in ReadOnlyTensorSpan x, in Span destination) + where TOperation : TensorOperation.IUnaryOperation_Tensor + { + scoped Span xIndexes = RentedBuffer.Create(x.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + nint destinationIndex = -1; + + for (nint i = 0; i < destination.Length; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(x._shape, xLinearOffset, xIndexes); + destinationIndex++; + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + ref Unsafe.Add(ref destination[0], destinationIndex) + ); + } + + xRentedBuffer.Dispose(); + } + + public static void Invoke(in ReadOnlyTensorSpan x, ref TResult destination) + where TOperation : TensorOperation.IUnaryReduction_Tensor + { + scoped Span xIndexes = RentedBuffer.Create(x.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + + for (nint i = 0; i < x.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(x._shape, xLinearOffset, xIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + ref destination + ); + } + + xRentedBuffer.Dispose(); + } + + public static void Invoke(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y, in TensorSpan destination) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Tensor + { + scoped Span xIndexes = RentedBuffer.Create(destination.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + scoped Span yIndexes = RentedBuffer.Create(destination.Rank, y.Strides, out nint yLinearOffset, out RentedBuffer yRentedBuffer); + scoped Span destinationIndexes = RentedBuffer.Create(destination.Rank, destination.Strides, out nint destinationLinearOffset, out RentedBuffer destinationRentedBuffer); + + for (nint i = 0; i < destination.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(destination._shape, xLinearOffset, xIndexes); + yLinearOffset = y._shape.AdjustToNextIndex(destination._shape, yLinearOffset, yIndexes); + destinationLinearOffset = destination._shape.AdjustToNextIndex(destination._shape, destinationLinearOffset, destinationIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + in Unsafe.Add(ref y._reference, yLinearOffset), + ref Unsafe.Add(ref destination._reference, destinationLinearOffset) + ); + } + + xRentedBuffer.Dispose(); + yRentedBuffer.Dispose(); + destinationRentedBuffer.Dispose(); + } + + public static void Invoke(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y, ref TResult result) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Tensor + { + ref readonly TensorShape destinationShape = ref ((x._shape.FlattenedLength > y._shape.FlattenedLength) ? ref x._shape : ref y._shape); + + scoped Span xIndexes = RentedBuffer.Create(destinationShape.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + scoped Span yIndexes = RentedBuffer.Create(destinationShape.Rank, y.Strides, out nint yLinearOffset, out RentedBuffer yRentedBuffer); + + for (nint i = 0; i < destinationShape.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(destinationShape, xLinearOffset, xIndexes); + yLinearOffset = y._shape.AdjustToNextIndex(destinationShape, yLinearOffset, yIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + in Unsafe.Add(ref y._reference, yLinearOffset), + ref result + ); + } + + xRentedBuffer.Dispose(); + yRentedBuffer.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(in ReadOnlyTensorSpan x, TArg y, in TensorSpan destination) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Scalar => Invoke(in x, y, in destination); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(in ReadOnlyTensorSpan x, int y, in TensorSpan destination) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Int32 => Invoke(in x, y, in destination); + + public static void Invoke(in ReadOnlyTensorSpan x, T2 y, in TensorSpan destination) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Scalar + { + scoped Span xIndexes = RentedBuffer.Create(destination.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + scoped Span destinationIndexes = RentedBuffer.Create(destination.Rank, destination.Strides, out nint destinationLinearOffset, out RentedBuffer destinationRentedBuffer); + + for (nint i = 0; i < destination.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(destination._shape, xLinearOffset, xIndexes); + destinationLinearOffset = destination._shape.AdjustToNextIndex(destination._shape, destinationLinearOffset, destinationIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + y, + ref Unsafe.Add(ref destination._reference, destinationLinearOffset) + ); + } + + xRentedBuffer.Dispose(); + destinationRentedBuffer.Dispose(); + } + + public static void Invoke(TArg x, in ReadOnlyTensorSpan y, in TensorSpan destination) + where TOperation : TensorOperation.IBinaryOperation_Scalar_Tensor + { + scoped Span xIndexes = RentedBuffer.Create(destination.Rank, y.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + scoped Span destinationIndexes = RentedBuffer.Create(destination.Rank, destination.Strides, out nint destinationLinearOffset, out RentedBuffer destinationRentedBuffer); + + for (nint i = 0; i < destination.FlattenedLength; i++) + { + xLinearOffset = y._shape.AdjustToNextIndex(destination._shape, xLinearOffset, xIndexes); + destinationLinearOffset = destination._shape.AdjustToNextIndex(destination._shape, destinationLinearOffset, destinationIndexes); + + TOperation.Invoke( + x, + in Unsafe.Add(ref y._reference, xLinearOffset), + ref Unsafe.Add(ref destination._reference, destinationLinearOffset) + ); + } + + xRentedBuffer.Dispose(); + destinationRentedBuffer.Dispose(); + } + + public static void Invoke(in ReadOnlyTensorSpan x, T2 y, ref TResult result) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Scalar + { + scoped Span xIndexes = RentedBuffer.Create(x.Rank, x.Strides, out nint xLinearOffset, out RentedBuffer xRentedBuffer); + + for (nint i = 0; i < x.FlattenedLength; i++) + { + xLinearOffset = x._shape.AdjustToNextIndex(x._shape, xLinearOffset, xIndexes); + + TOperation.Invoke( + in Unsafe.Add(ref x._reference, xLinearOffset), + y, + ref result + ); + } + + xRentedBuffer.Dispose(); + } + + public static void Invoke(in ReadOnlyTensorSpan x, TArg y, ref TResult result) + where TOperation : TensorOperation.IBinaryOperation_Tensor_Scalar => Invoke(in x, y, ref result); + + public static void ValidateCompatibility(in ReadOnlyTensorSpan x, in ReadOnlySpan lengths) + { + // x can be broadcast to destination, not vice verse + if (!TensorShape.AreCompatible(lengths, x._shape)) + ThrowHelper.ThrowArgument_LengthsNotCompatible(); + } + + public static void ValidateCompatibility(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y) + { + // Can be bidirectional validation + if (!TensorShape.AreCompatible(x._shape, y._shape, true)) + ThrowHelper.ThrowArgument_LengthsNotCompatible(); + } + + public static void ValidateCompatibility(in ReadOnlyTensorSpan x, in TensorSpan destination) + { + // x can be broadcast to destination, not vice verse + if (!TensorShape.AreCompatible(destination._shape, x._shape, false)) + ThrowHelper.ThrowArgument_LengthsNotCompatible(); + } + + public static void ValidateCompatibility(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y, in TensorSpan destination) + { + // can do bidirectional validation between x and y, that result can then be broadcast to destination + if (TensorShape.AreCompatible(x._shape, y._shape, true)) + { + if (TensorShape.AreCompatible(destination._shape, x._shape, false)) + { + if (TensorShape.AreCompatible(destination._shape, y._shape, false)) + { + // all three are compatible + return; + } + } + } + ThrowHelper.ThrowArgument_LengthsNotCompatible(); + } + + public static void ValidateCompatibility(in ReadOnlyTensorSpan x, in ReadOnlyTensorSpan y, out Tensor destination) + { + // can do bidirectional validation between x and y, that result can then be broadcast to destination + if (TensorShape.AreCompatible(x._shape, y._shape, true)) + { + if (x.Rank > y.Rank) + { + destination = Tensor.CreateUninitialized(x._shape.Lengths); + } + else + { + destination = Tensor.CreateUninitialized(y._shape.Lengths); + } + return; + } + destination = default!; + ThrowHelper.ThrowArgument_LengthsNotCompatible(); + } + + public readonly struct Clear + : IOperation + { + public static void Invoke(ref T destination) + { + destination = default!; + } + + public static void Invoke(Span destination) + { + destination.Clear(); + } + } + + public readonly struct CopyTo + : IUnaryOperation_Tensor + { + public static void Invoke(ref readonly T source, ref T destination) + { + destination = source; + } + + public static void Invoke(ReadOnlySpan source, Span destination) + { + source.CopyTo(destination); + } + } + + public readonly struct Equals + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IEqualityOperators + { + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = (left == right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = (left == right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] == right); + } + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] == right[i]); + } + } + } + + public readonly struct EqualsAny + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IEqualityOperators + { + // The main loop early exits at the first false condition, so we + // check x != y and returns false on first equal. The consumer will + // then negate whatever the main loop returns as `true` means none + // are equal. + + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = (left != right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = (left != right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = (left[i] != right); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = (left[i] != right[i]); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + } + + #region TensorOperation Primitives + public readonly struct Abs + : IUnaryOperation_Tensor + where T : INumberBase + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Abs(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Abs(x, destination); + } + } + + public readonly struct Acos + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Acos(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Acos(x, destination); + } + } + + public readonly struct Acosh + : IUnaryOperation_Tensor + where T : IHyperbolicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Acosh(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Acosh(x, destination); + } + } + + public readonly struct AcosPi + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.AcosPi(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.AcosPi(x, destination); + } + } + + public readonly struct Add + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IAdditionOperators, IAdditiveIdentity + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = x + y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Add(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = x + y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Add(x, y, destination); + } + } + + public readonly struct Asin + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Asin(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Asin(x, destination); + } + } + + public readonly struct Asinh + : IUnaryOperation_Tensor + where T : IHyperbolicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Asinh(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Asinh(x, destination); + } + } + + public readonly struct AsinPi + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.AsinPi(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.AsinPi(x, destination); + } + } + + public readonly struct Atan + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Atan(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Atan(x, destination); + } + } + + public readonly struct Atan2 + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IFloatingPointIeee754 + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Atan2(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = T.Atan2(x[i], y); + } + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Atan2(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Atan2(x, y, destination); + } + } + + public readonly struct Atan2Pi + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IFloatingPointIeee754 + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Atan2Pi(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = T.Atan2Pi(x[i], y); + } + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Atan2Pi(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Atan2Pi(x, y, destination); + } + } + + public readonly struct Atanh + : IUnaryOperation_Tensor + where T : IHyperbolicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Atanh(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Atanh(x, destination); + } + } + + public readonly struct AtanPi + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.AtanPi(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.AtanPi(x, destination); + } + } + + public readonly struct BitwiseAnd + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IBitwiseOperators + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = x & y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.BitwiseAnd(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = x & y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.BitwiseAnd(x, y, destination); + } + } + + public readonly struct BitwiseOr + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IBitwiseOperators + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = x | y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.BitwiseOr(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = x | y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.BitwiseOr(x, y, destination); + } + } + + public readonly struct Cbrt + : IUnaryOperation_Tensor + where T : IRootFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Cbrt(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Cbrt(x, destination); + } + } + + public readonly struct Ceiling + : IUnaryOperation_Tensor + where T : IFloatingPoint + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Ceiling(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Ceiling(x, destination); + } + } + + public readonly struct ConvertChecked + : IUnaryOperation_Tensor + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + public static void Invoke(ref readonly TFrom x, ref TTo destination) + { + destination = TTo.CreateChecked(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.ConvertChecked(x, destination); + } + } + + public readonly struct ConvertSaturating + : IUnaryOperation_Tensor + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + public static void Invoke(ref readonly TFrom x, ref TTo destination) + { + destination = TTo.CreateSaturating(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.ConvertSaturating(x, destination); + } + } + + public readonly struct ConvertTruncating + : IUnaryOperation_Tensor + where TFrom : IEquatable, IEqualityOperators, INumberBase + where TTo : INumberBase + { + public static void Invoke(ref readonly TFrom x, ref TTo destination) + { + destination = TTo.CreateTruncating(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.ConvertTruncating(x, destination); + } + } + + public readonly struct CopySign + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumber + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.CopySign(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.CopySign(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.CopySign(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.CopySign(x, y, destination); + } + } + + public readonly struct Cos + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Cos(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Cos(x, destination); + } + } + + public readonly struct Cosh + : IUnaryOperation_Tensor + where T : IHyperbolicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Cosh(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Cosh(x, destination); + } + } + + public struct CosineSimilarity + : IBinaryOperation_Tensor_Tensor> + where T : IRootFunctions + { + /// This method effectively computes TensorPrimitives.Dot(x, y) / (.Sqrt(TensorPrimitives.SumOfSquares(x)) * .Sqrt(TensorPrimitives.SumOfSquares(y)). + + public static void Invoke(ref readonly T x, ref readonly T y, ref (T, T, T) destination) + { + destination.Item1 += (x * y); + destination.Item2 += (x * x); + destination.Item3 += (y * y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span<(T, T, T)> destination) + { + destination[0].Item1 += TensorPrimitives.Dot(x, y); + destination[0].Item2 += TensorPrimitives.SumOfSquares(x); + destination[0].Item3 += TensorPrimitives.SumOfSquares(y); + } + } + + public readonly struct CosPi + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.CosPi(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.CosPi(x, destination); + } + } + + public readonly struct DegreesToRadians + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.DegreesToRadians(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.DegreesToRadians(x, destination); + } + } + + public readonly struct Divide + : IBinaryOperation_Scalar_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IDivisionOperators + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = x / y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Divide(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = x / y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Divide(x, y, destination); + } + + public static void Invoke(T x, ref readonly T y, ref T destination) + { + destination = x / y; + } + public static void Invoke(T x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Divide(x, y, destination); + } + } + + public struct Dot + : IBinaryOperation_Tensor_Tensor + where T : IAdditionOperators, IAdditiveIdentity, IMultiplicativeIdentity, IMultiplyOperators + { + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination += x * y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + destination[0] += TensorPrimitives.Dot(x, y); + } + } + + public readonly struct Exp + : IUnaryOperation_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Exp(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Exp(x, destination); + } + } + + public readonly struct Exp10 + : IUnaryOperation_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Exp10(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Exp10(x, destination); + } + } + + public readonly struct Exp10M1 + : IUnaryOperation_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Exp10M1(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Exp10M1(x, destination); + } + } + + public readonly struct Exp2 + : IUnaryOperation_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Exp2(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Exp2(x, destination); + } + } + + public readonly struct Exp2M1 + : IUnaryOperation_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Exp2M1(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Exp2M1(x, destination); + } + } + + public readonly struct ExpM1 + : IUnaryOperation_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.ExpM1(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.ExpM1(x, destination); + } + } + + public readonly struct Floor + : IUnaryOperation_Tensor + where T : IFloatingPoint + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Floor(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Floor(x, destination); + } + } + + public readonly struct Hypot + : IBinaryOperation_Tensor_Tensor + where T : IRootFunctions + { + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Hypot(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Hypot(x, y, destination); + } + } + + public readonly struct Ieee754Remainder + : IBinaryOperation_Scalar_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IFloatingPointIeee754 + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Ieee754Remainder(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Ieee754Remainder(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Ieee754Remainder(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Ieee754Remainder(x, y, destination); + } + + public static void Invoke(T x, ref readonly T y, ref T destination) + { + destination = T.Ieee754Remainder(x, y); + } + public static void Invoke(T x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Ieee754Remainder(x, y, destination); + } + } + + public readonly struct ILogB + : IUnaryOperation_Tensor + where T : IFloatingPointIeee754 + { + public static void Invoke(ref readonly T x, ref int destination) + { + destination = T.ILogB(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.ILogB(x, destination); + } + } + + public readonly struct LeadingZeroCount + : IUnaryOperation_Tensor + where T : IBinaryInteger + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.LeadingZeroCount(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.LeadingZeroCount(x, destination); + } + } + + public readonly struct Log + : IUnaryOperation_Tensor, + IBinaryOperation_Tensor_Tensor, + IBinaryOperation_Tensor_Scalar + where T : ILogarithmicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Log(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Log(x, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Log(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Log(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Log(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Log(x, y, destination); + } + } + + public readonly struct Log10 + : IUnaryOperation_Tensor + where T : ILogarithmicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Log10(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Log10(x, destination); + } + } + + public readonly struct Log10P1 + : IUnaryOperation_Tensor + where T : ILogarithmicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Log10P1(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Log10P1(x, destination); + } + } + + public readonly struct Log2 + : IUnaryOperation_Tensor + where T : ILogarithmicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Log2(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Log2(x, destination); + } + } + + public readonly struct Log2P1 + : IUnaryOperation_Tensor + where T : ILogarithmicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Log2P1(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Log2P1(x, destination); + } + } + + public readonly struct LogP1 + : IUnaryOperation_Tensor + where T : ILogarithmicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.LogP1(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.LogP1(x, destination); + } + } + + public struct Max + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumber + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Max(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.Max(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Max(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Max(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Max(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Max(x, y, destination); + } + } + + public struct MaxMagnitude + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumber + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.MaxMagnitude(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.MaxMagnitude(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.MaxMagnitude(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.MaxMagnitude(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.MaxMagnitude(x, destination); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.MaxMagnitude(x, y, destination); + } + } + + public struct MaxMagnitudeNumber + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumberBase + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.MaxMagnitudeNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.MaxMagnitudeNumber(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.MaxMagnitudeNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.MaxMagnitudeNumber(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.MaxMagnitudeNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.MaxMagnitudeNumber(x, y, destination); + } + } + + public struct MaxNumber + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumber + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.MaxNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.MaxNumber(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.MaxNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.MaxNumber(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.MaxNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.MaxNumber(x, y, destination); + } + } + + public struct Min + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumber + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Min(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.Min(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Min(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Min(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Min(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Min(x, y, destination); + } + } + + public struct MinMagnitude + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumber + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.MinMagnitude(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.MinMagnitude(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.MinMagnitude(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.MinMagnitude(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.MinMagnitude(x, destination); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.MinMagnitude(x, y, destination); + } + } + + public struct MinMagnitudeNumber + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumberBase + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.MinMagnitudeNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.MinMagnitudeNumber(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.MinMagnitudeNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.MinMagnitudeNumber(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.MinMagnitudeNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.MinMagnitudeNumber(x, y, destination); + } + } + + public struct MinNumber + : IUnaryReduction_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : INumber + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.MinNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.MinNumber(x); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.MinNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.MinNumber(x, y, destination); + } + + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.MinNumber(x, destination); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.MinNumber(x, y, destination); + } + } + + public readonly struct Multiply + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IMultiplyOperators, IMultiplicativeIdentity + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = x * y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Multiply(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = x * y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Multiply(x, y, destination); + } + } + + public readonly struct Negate + : IUnaryOperation_Tensor + where T : IUnaryNegationOperators + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = -x; + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Negate(x, destination); + } + } + + public readonly struct OnesComplement + : IUnaryOperation_Tensor + where T : IBitwiseOperators + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = ~x; + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.OnesComplement(x, destination); + } + } + + public readonly struct PopCount + : IUnaryOperation_Tensor + where T : IBinaryInteger + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.PopCount(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.PopCount(x, destination); + } + } + + public readonly struct Pow + : IBinaryOperation_Scalar_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IPowerFunctions + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Pow(x, y); + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Pow(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = T.Pow(x, y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Pow(x, y, destination); + } + + public static void Invoke(T x, ref readonly T y, ref T destination) + { + destination = T.Pow(x, y); + } + + public static void Invoke(T x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Pow(x, y, destination); + } + } + + public struct Product + : IUnaryReduction_Tensor + where T : IMultiplicativeIdentity, IMultiplyOperators + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination *= x; + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination = TensorPrimitives.Product(x); + } + } + + public readonly struct RadiansToDegrees + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.RadiansToDegrees(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.RadiansToDegrees(x, destination); + } + } + + public readonly struct Reciprocal + : IUnaryOperation_Tensor + where T : IFloatingPoint + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.One / x; + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Reciprocal(x, destination); + } + } + + public readonly struct RootN + : IBinaryOperation_Tensor_Int32 + where T : IRootFunctions + { + public static void Invoke(ref readonly T x, int y, ref T destination) + { + destination = T.RootN(x, y); + } + + public static void Invoke(ReadOnlySpan x, int y, Span destination) + { + TensorPrimitives.RootN(x, y, destination); + } + } + + public readonly struct RotateLeft + : IBinaryOperation_Tensor_Int32 + where T : IBinaryInteger + { + public static void Invoke(ref readonly T x, int y, ref T destination) + { + destination = T.RotateLeft(x, y); + } + public static void Invoke(ReadOnlySpan x, int y, Span destination) + { + TensorPrimitives.RotateLeft(x, y, destination); + } + } + + public readonly struct RotateRight + : IBinaryOperation_Tensor_Int32 + where T : IBinaryInteger + { + public static void Invoke(ref readonly T x, int y, ref T destination) + { + destination = T.RotateRight(x, y); + } + public static void Invoke(ReadOnlySpan x, int y, Span destination) + { + TensorPrimitives.RotateRight(x, y, destination); + } + } + + public readonly struct Round + : IUnaryOperation_Tensor, + IBinaryOperation_Tensor_Scalar, T> + where T : IFloatingPoint + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Round(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Round(x, destination); + } + + public static void Invoke(ref readonly T x, Tuple y, ref T destination) + { + destination = T.Round(x, y.Item1, y.Item2); + } + + public static void Invoke(ReadOnlySpan x, Tuple y, Span destination) + { + TensorPrimitives.Round(x, y.Item1, y.Item2, destination); + } + } + + public readonly struct Sigmoid + : IUnaryOperation_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.One / (T.One + T.Exp(-x)); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Sigmoid(x, destination); + } + } + + public readonly struct Sin + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Sin(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Sin(x, destination); + } + } + + public readonly struct Sinh + : IUnaryOperation_Tensor + where T : IHyperbolicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Sinh(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Sinh(x, destination); + } + } + + public readonly struct SinPi + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.SinPi(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.SinPi(x, destination); + } + } + + // SoftMax Helper + public readonly struct SumExp + : IUnaryReduction_Tensor + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination += T.Exp(x); + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + for (int i = 0; i < x.Length; i++) + { + destination += T.Exp(x[i]); + } + } + } + + public readonly struct SoftMax + : IBinaryOperation_Tensor_Scalar + where T : IExponentialFunctions + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = T.Exp(x) / y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + for (int i = 0; i < x.Length; i++) + { + destination[i] = T.Exp(x[i]) / y; + } + } + } + + public readonly struct Sqrt + : IUnaryOperation_Tensor + where T : IRootFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Sqrt(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Sqrt(x, destination); + } + } + + public readonly struct Subtract + : IBinaryOperation_Scalar_Tensor, + IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : ISubtractionOperators + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = x - y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Subtract(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = x - y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Subtract(x, y, destination); + } + + public static void Invoke(T x, ref readonly T y, ref T destination) + { + destination = x - y; + } + + public static void Invoke(T x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Subtract(x, y, destination); + } + } + + public struct Sum + : IUnaryReduction_Tensor + where T : IAdditionOperators, IAdditiveIdentity + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination += x; + } + + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination += TensorPrimitives.Sum(x); + } + } + + public struct SumOfSquaredDifferences + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IAdditionOperators, IAdditiveIdentity, IMultiplyOperators, ISubtractionOperators + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination += (x - y) * (x - y); + } + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + for (int i = 0; i < x.Length; i++) + { + destination[i] = (x[i] - y) * (x[i] - y); + } + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination += (x - y) * (x - y); + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + for (int i = 0; i < x.Length; i++) + { + destination[i] = (x[i] - y[i]) * (x[i] - y[i]); + } + } + } + + public readonly struct SumOfSquares + : IUnaryReduction_Tensor + where T : IAdditionOperators, IAdditiveIdentity, IMultiplyOperators + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination += x * x; + } + public static void Invoke(ReadOnlySpan x, ref T destination) + { + destination += TensorPrimitives.SumOfSquares(x); + } + } + + public readonly struct Tan + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Tan(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Tan(x, destination); + } + } + + public readonly struct Tanh + : IUnaryOperation_Tensor + where T : IHyperbolicFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Tanh(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Tanh(x, destination); + } + } + + public readonly struct TanPi + : IUnaryOperation_Tensor + where T : ITrigonometricFunctions + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.TanPi(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.TanPi(x, destination); + } + } + + public readonly struct TrailingZeroCount + : IUnaryOperation_Tensor + where T : IBinaryInteger + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.TrailingZeroCount(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.TrailingZeroCount(x, destination); + } + } + + public readonly struct Truncate + : IUnaryOperation_Tensor + where T : IFloatingPoint + { + public static void Invoke(ref readonly T x, ref T destination) + { + destination = T.Truncate(x); + } + + public static void Invoke(ReadOnlySpan x, Span destination) + { + TensorPrimitives.Truncate(x, destination); + } + } + + public readonly struct Xor + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IBitwiseOperators + { + public static void Invoke(ref readonly T x, T y, ref T destination) + { + destination = x ^ y; + } + + public static void Invoke(ReadOnlySpan x, T y, Span destination) + { + TensorPrimitives.Xor(x, y, destination); + } + + public static void Invoke(ref readonly T x, ref readonly T y, ref T destination) + { + destination = x ^ y; + } + + public static void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + TensorPrimitives.Xor(x, y, destination); + } + } + + #endregion + + public readonly struct Fill + : IUnaryOperation_Scalar + { + public static void Invoke(ref T destination, T value) + { + destination = value; + } + + public static void Invoke(Span destination, T value) + { + destination.Fill(value); + } + } + + public readonly struct GreaterThan + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = (left > right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = (left > right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] > right); + } + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] > right[i]); + } + } + } + + public readonly struct GreaterThanAny + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so we + // check !(x > y) and returns false on first not greater. The consumer will + // then negate whatever the main loop returns as `true` means none + // are greater. + + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = !(left > right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = !(left > right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] > right); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] > right[i]); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + } + + public readonly struct GreaterThanOrEqual + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = (left >= right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = (left >= right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] >= right); + } + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] >= right[i]); + } + } + } + + public readonly struct GreaterThanOrEqualAny + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so we + // check !(x >= y) and returns false on first not greater or equal. + // The consumer will then negate whatever the main loop returns as + // `true` means none are greater or equal. + + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = !(left >= right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = !(left >= right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] >= right); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] >= right[i]); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + } + + public readonly struct LessThan + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = (left < right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = (left < right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] < right); + } + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] < right[i]); + } + } + } + + public readonly struct LessThanAny + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so we + // check !(x < y) and returns false on first not lesser. The consumer will + // then negate whatever the main loop returns as `true` means none + // are lesser. + + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = !(left < right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = !(left < right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] < right); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] < right[i]); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + } + + public readonly struct LessThanOrEqual + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = (left <= right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = (left <= right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] <= right); + } + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = (left[i] <= right[i]); + } + } + } + + public readonly struct LessThanOrEqualAny + : IBinaryOperation_Tensor_Scalar, + IBinaryOperation_Tensor_Tensor + where T : IComparisonOperators + { + // The main loop early exits at the first false condition, so we + // check !(x <= y) and returns false on first not lesser or equal. + // The consumer will then negate whatever the main loop returns as + // `true` means none are lesser or equal. + + public static void Invoke(ref readonly T left, T right, ref bool destination) + { + destination = !(left <= right); + } + + public static void Invoke(ref readonly T left, ref readonly T right, ref bool destination) + { + destination = !(left <= right); + } + + public static void Invoke(ReadOnlySpan left, T right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] <= right); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + + public static void Invoke(ReadOnlySpan left, ReadOnlySpan right, Span destination) + { + Debug.Assert(destination.Length == 1); + bool result = false; + + for (int i = 0; i < destination.Length; i++) + { + result = !(left[i] <= right[i]); + + if (!result) + { + break; + } + } + + destination[0] = result; + } + } + + public interface IBinaryOperation_Tensor_Scalar + : IBinaryOperation_Tensor_Scalar + { + } + + public interface IBinaryOperation_Tensor_Int32 + : IBinaryOperation_Tensor_Scalar + { + } + + public interface IBinaryOperation_Tensor_Scalar + { + static abstract void Invoke(ref readonly T1 x, T2 y, ref TResult destination); + static abstract void Invoke(ReadOnlySpan x, T2 y, Span destination); + } + + public interface IBinaryOperation_Scalar_Tensor + { + static abstract void Invoke(T1 x, ref readonly T2 y, ref TResult destination); + static abstract void Invoke(T1 x, ReadOnlySpan y, Span destination); + } + + public interface IBinaryOperation_Tensor_Tensor + { + static abstract void Invoke(ref readonly T x, ref readonly T y, ref TResult destination); + static abstract void Invoke(ReadOnlySpan x, ReadOnlySpan y, Span destination); + } + + public interface IOperation + { + static abstract void Invoke(ref T destination); + static abstract void Invoke(Span destination); + } + + public interface IUnaryOperation_Scalar + { + static abstract void Invoke(ref TResult destination, T x); + static abstract void Invoke(Span destination, T x); + } + + public interface IUnaryOperation_Tensor + { + static abstract void Invoke(ref readonly T x, ref TResult destination); + static abstract void Invoke(ReadOnlySpan x, Span destination); + } + + public interface IUnaryReduction_Tensor + { + static abstract void Invoke(ref readonly T x, ref TResult destination); + static abstract void Invoke(ReadOnlySpan x, ref TResult destination); + } + + internal readonly struct RentedBuffer + { + public static Span Create(int rank, ReadOnlySpan strides, out nint linearOffset, [UnscopedRef] out RentedBuffer rentedBuffer) + where T : INumber + { + Span output = RentedBuffer.Create(rank, out rentedBuffer); + linearOffset = 0 - (!strides.IsEmpty ? strides[^1] : 0); + + output[^1] = T.CreateChecked(-1); + return output; + } + + public static Span CreateUninitialized(int rank, [UnscopedRef] out RentedBuffer rentedBuffer) + => RentedBuffer.Create(rank, out rentedBuffer); + } + + internal ref struct RentedBuffer : IDisposable + { + private T[]? _array; + private TensorShape.InlineBuffer _inline; + + public static Span Create(int rank, [UnscopedRef] out RentedBuffer rentedBuffer) + { + if (rank > TensorShape.MaxInlineRank) + { + rentedBuffer._array = ArrayPool.Shared.Rent(rank); + Unsafe.SkipInit(out rentedBuffer._inline); + + Span resultBuffer = rentedBuffer._array.AsSpan(0, rank); + resultBuffer.Clear(); + return resultBuffer; + } + else + { + rentedBuffer._array = null; + rentedBuffer._inline = default; + + return ((Span)rentedBuffer._inline)[..rank]; + } + } + + public void Dispose() + { + if (_array is not null) + { + ArrayPool.Shared.Return(_array); + _array = null; + } + } + } + } +} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs index 5aa155efff6a46..9cf04855e93d5d 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorShape.cs @@ -1,66 +1,1261 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.VisualBasic; namespace System.Numerics.Tensors { + // TensorShape tracks the core information required to safely interact with memory + // for tensors and tensor spans alike. + // + // We avoid allocating for small ranks up to MaxInlineRank in size and allocate a + // single buffer for larger ranks. This buffer will always be precisely `InlineBufferCount * rank` + // in size where the first rank elements are the length, the next rank elements are + // the strides, and then the next rank elements are the rank order. + // + // We cache both a flattened length and a linear length to avoid recalculating these + // key properties. The flattened length is the total number of elements represented + // by the tensor, however due to implicit broadcasting this may be greater than the + // amount of memory that actually backs the tensor. While the linear length is the + // backing storage size that is present. We can also have arbitrary strides, such as + // lengths: [4], strides: [2]. In this case the flattenedLength = 4, while the + // linearLength: 8. We can also have a slice of a tensor, such as lengths: [2, 2], + // strides: [3, 1] (which could have been taken from a 3x3 contiguous and dense tensor). + // + // These invariants allow us to safely and efficiently index into the backing storage + // as we know that memory functionally wraps around after linearLength elements. It + // also means that we only support broadcasting to greater dimensions and thus lengths + // strides and the backing memory form a strict relationship that is validated by the + // public constructors. + + [Flags] + internal enum TensorFlags : uint + { + None = 0, + IsContiguousAndDense = (1 << 0), + IsBroadcast = (1 << 1), + } [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - internal readonly struct TensorShape + internal readonly struct TensorShape : IEquatable { - // Used to determine when we need to allocate a metadata array - public const int MaxInlineArraySize = 5; + // The layout of the fields here is very particular and is intentionally designed to + // be compact and cache-friendly. The current size on a 64-bit system is 108+4 bytes + // and this fits within 2 cache lines, where 64 bytes is the typical cache line size. + // + // The TensorSpan and ReadOnlyTensorSpan types then track a byref field which takes + // an additional 8 bytes. This leaves 8 bytes still available for use for other scenarios + // if required. + + internal const int MaxInlineRank = 4; + private const int InlineBufferCount = 3; + + private readonly nint[]? _metadata; // 8 bytes + + private readonly nint _flattenedLength; // 8 bytes + private readonly nint _linearLength; // 8 bytes + + private readonly InlineBuffer _inlineLengths; // 4x8 bytes (32) + private readonly InlineBuffer _inlineStrides; // 4x8 bytes (32) + private readonly InlineBuffer _inlineLinearRankOrder; // 4x4 bytes (16) + + private readonly int _rank; + private readonly TensorFlags _flags; // 4 bytes + + private TensorShape(nint linearLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, scoped ReadOnlySpan linearRankOrder) + { + int rank = lengths.Length; + + if (rank == 0) + { + lengths = [0]; + rank = 1; + } + Debug.Assert(rank >= 1); + + scoped Span destinationLengths; + scoped Span destinationStrides; + scoped Span destinationLinearRankOrder; + + if (rank > MaxInlineRank) + { + nint[] metadata = new nint[rank * InlineBufferCount]; + + destinationLengths = metadata.AsSpan(rank * 0, rank); + destinationStrides = metadata.AsSpan(rank * 1, rank); + destinationLinearRankOrder = MemoryMarshal.CreateSpan( + ref Unsafe.As(ref metadata[rank * 2]), + rank + ); + + _metadata = metadata; + } + else + { + destinationLengths = ((Span)_inlineLengths)[..rank]; + destinationStrides = ((Span)_inlineStrides)[..rank]; + destinationLinearRankOrder = ((Span)_inlineLinearRankOrder)[..rank]; + } + + if (linearRankOrder.Length == 0) + { + // The linearRankOrder is expected to be in "row-major" order, that is otherwise + // known as "big-endian" order where the dimensions that are farthest apart + // (i.e. have the greatest impact to computing a linear index) appear first. + // + // So, when no rank order is specified by the user we simply populate this + // as 0 to rank-1. In the case strides is also not specified, this will be + // correct "as is"; otherwise, we will sort this based on which stride is + // largest prior to validating the user provided strides. + + for (int i = 0; i < destinationLinearRankOrder.Length; i++) + { + destinationLinearRankOrder[i] = i; + } + } + else if (linearRankOrder.Length != rank) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + else + { + // If a rank order was specified, then we need to ensure that it is valid, + // which should mean that when sorting we have values from 0 to rank-1. + // + // While this does copy the rank order twice, the typical rank is expected + // to be small and so the cost should be minimal. + + linearRankOrder.CopyTo(destinationLinearRankOrder); + destinationLinearRankOrder.Sort(); + + for (int i = 0; i < linearRankOrder.Length; i++) + { + if (destinationLinearRankOrder[i] != i) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + } + + linearRankOrder.CopyTo(destinationLinearRankOrder); + } + + nint flattenedLength = 1; + nint maximumLinearIndex = 0; + + if (strides.Length == 0) + { + // When no strides are specified, we need to computing them based on the given + // rank order. We use destinationLinearRankOrder here to ensure that we have a + // correct order even if no rank order was specified by the user. + // + // To do this, we simply iterate the rank order from least to most significant + // so that the strides match the expected order, being the product of all previous + // dimension lengths. + + for (int i = 0; i < destinationLinearRankOrder.Length; i++) + { + int rankIndex = destinationLinearRankOrder.Length - (i + 1); + int linearRankIndex = destinationLinearRankOrder[rankIndex]; + + nint length = lengths[linearRankIndex]; + + if (length < 0) + { + ThrowHelper.ThrowArgument_LengthIsNegative(); + } + + nint stride = flattenedLength; + + if (length > 1) + { + maximumLinearIndex = checked(maximumLinearIndex + ((length - 1) * stride)); + } + else + { + stride = 0; + _flags |= TensorFlags.IsBroadcast; + } + + destinationLengths[linearRankIndex] = length; + destinationStrides[linearRankIndex] = stride; + + flattenedLength = checked(flattenedLength * length); + } + } + else if (strides.Length != lengths.Length) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + else + { + // If a strides was specified, then we need to ensure it is valid as well, + // which should mean that when sorted by rank order (most to least significant) + // each stride should be greater than or equal to the previous stride multiplied + // by the dimension length. + // + // The reason it is "or equal" and not simply "greater than" is because a dimension + // can be length 1 and thus the stride is the same as the previous rank. + // + // Additionally, when sorted we allow for the first n (most significant) strides to be + // specified as 0 in which case we automatically compute that to be the same as stride + // n+1. This makes it convenient to support implicit broadcasting where higher dimensions + // aren't actually stored in memory. + + nint minimumNonZeroStride = 0; + var sortedWithIndex = destinationLinearRankOrder.ToArray() + .Select((value, index) => new { Value = value, Index = index }) + .OrderBy(x => x.Value) + .ToArray(); + + int[] sortedOrder = sortedWithIndex.Select(x => x.Index).ToArray(); + for (int i = 0; i < destinationLinearRankOrder.Length; i++) + { + int rankIndex = destinationLinearRankOrder.Length - (i + 1); + int linearRankIndex = sortedOrder[rankIndex]; - // Used to determine when we can stack alloc for indexing vs when we need to allocate - public const int MaxInlineRank = 8; + nint length = lengths[linearRankIndex]; - internal readonly nint[]? _metadata; // 8 bytes + if (length < 0) + { + ThrowHelper.ThrowArgument_LengthIsNegative(); + } - internal readonly nint _memoryLength; // 8 bytes - internal readonly int _rank; // 4 bytes + nint stride = strides[linearRankIndex]; - private readonly NintBuffer _lengths; - private readonly NintBuffer _strides; + if (stride < 0) + { + ThrowHelper.ThrowArgument_StrideIsNegative(); + } - internal TensorShape(nint memoryLength, ReadOnlySpan lengths, ReadOnlySpan strides) + if (stride != 0) + { + if (stride < minimumNonZeroStride) + { + // The next stride needs to be at least as big as the + // previous stride times the dimension length, otherwise + // the linear rank ordering is incorrect + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + + if (length <= 1) + { + // We require the stride to be zero if the dimension length + // is 0 or 1, as this is necessary for indexing with broadcast to + // work as expected. + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + + minimumNonZeroStride = checked(length * stride); + maximumLinearIndex = checked(maximumLinearIndex + (minimumNonZeroStride - stride)); + } + else + { + _flags |= TensorFlags.IsBroadcast; + } + + destinationLengths[linearRankIndex] = length; + destinationStrides[linearRankIndex] = stride; + + flattenedLength = checked(flattenedLength * length); + } + } + + // Once we've finished computing everything physically present in the input + // we need to ensure that the linearLength is greater than or equal to the + // minimumLinearLength so that lengths is in range of the backing buffer and + // so that we can support broadcasting for anything that was length 1. + + nint minimumLinearLength = (flattenedLength != 0) ? (maximumLinearIndex + 1) : 0; + + if (linearLength != -1) + { + ArgumentOutOfRangeException.ThrowIfLessThan(linearLength, minimumLinearLength); + } + + _flattenedLength = flattenedLength; + _linearLength = minimumLinearLength; + + _rank = rank; + + if (flattenedLength == minimumLinearLength) + { + // When the flattenedLength and minimumLinearLength are the same, then the + // underlying buffer is "contiguous and dense" which allows various other + // optimizations to be utilized when processing the tensor. + _flags |= TensorFlags.IsContiguousAndDense; + } + + ValidateState(); + } + + private TensorShape(nint flattenedLength, nint linearLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, scoped ReadOnlySpan linearRankOrder, int rank, TensorFlags flags) { - _memoryLength = memoryLength; - _rank = lengths.Length; - if (lengths.Length > MaxInlineArraySize) + Debug.Assert(lengths.Length == rank); + Debug.Assert(strides.Length == rank); + Debug.Assert(linearRankOrder.Length == rank); + + scoped Span destinationLengths; + scoped Span destinationStrides; + scoped Span destinationLinearRankOrder; + + if (rank > MaxInlineRank) { - _metadata = new nint[lengths.Length + strides.Length]; - lengths.CopyTo(MemoryMarshal.CreateSpan(ref _metadata[0], lengths.Length)); - strides.CopyTo(MemoryMarshal.CreateSpan(ref _metadata[lengths.Length], strides.Length)); + nint[] metadata = new nint[rank * InlineBufferCount]; + + destinationLengths = metadata.AsSpan(rank * 0, rank); + destinationStrides = metadata.AsSpan(rank * 1, rank); + destinationLinearRankOrder = MemoryMarshal.CreateSpan( + ref Unsafe.As(ref metadata[rank * 2]), + rank + ); + + _metadata = metadata; } else { - lengths.CopyTo(_lengths); - strides.CopyTo(_strides); + destinationLengths = ((Span)_inlineLengths)[..rank]; + destinationStrides = ((Span)_inlineStrides)[..rank]; + destinationLinearRankOrder = ((Span)_inlineLinearRankOrder)[..rank]; } + + _flattenedLength = flattenedLength; + _linearLength = linearLength; + + lengths.CopyTo(destinationLengths); + strides.CopyTo(destinationStrides); + linearRankOrder.CopyTo(destinationLinearRankOrder); + + _rank = rank; + _flags = flags; + + ValidateState(); } - [InlineArray(MaxInlineArraySize)] // 5x8 bytes (40) - private struct NintBuffer + public bool IsBroadcast => (_flags & TensorFlags.IsBroadcast) != 0; + + public bool IsContiguousAndDense => (_flags & TensorFlags.IsContiguousAndDense) != 0; + + public nint FlattenedLength => _flattenedLength; + + public bool IsEmpty => _flattenedLength == 0; + + public nint LinearLength => _linearLength; + + [UnscopedRef] + public ReadOnlySpan Lengths { - public nint e0; + get + { + if (_metadata is not nint[] metadata) + { + return ((ReadOnlySpan)_inlineLengths)[.._rank]; + } + else + { + int rank = metadata.Length / InlineBufferCount; + return metadata.AsSpan(rank * 0, rank); + } + } } [UnscopedRef] - public ReadOnlySpan Lengths => (_metadata is null) - ? ((ReadOnlySpan)_lengths).Slice(0, _rank) - : MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetArrayDataReference(_metadata), _rank); + public ReadOnlySpan LinearRankOrder + { + get + { + if (_metadata is not nint[] metadata) + { + return ((ReadOnlySpan)_inlineLinearRankOrder)[.._rank]; + } + else + { + int rank = metadata.Length / InlineBufferCount; + return MemoryMarshal.CreateSpan( + ref Unsafe.As(ref metadata[rank * 2]), + rank + ); + } + } + } + + public int Rank => _rank; [UnscopedRef] - public ReadOnlySpan Strides => (_metadata is null) - ? ((ReadOnlySpan)_strides).Slice(0, _rank) - : MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetArrayDataReference(_metadata), _rank * 2).Slice(_rank); + public ReadOnlySpan Strides + { + get + { + if (_metadata is not nint[] metadata) + { + return ((ReadOnlySpan)_inlineStrides)[.._rank]; + } + else + { + int rank = metadata.Length / InlineBufferCount; + return metadata.AsSpan(rank * 1, rank); + } + } + } + + public static bool operator ==(in TensorShape left, in TensorShape right) + { + int rank = left.Rank; + + if (rank != right.Rank) + { + return false; + } + + if (left.FlattenedLength != right.FlattenedLength) + { + return false; + } - public nint FlattenedLength => TensorSpanHelpers.CalculateTotalLength(Lengths); + if (left.LinearLength != right.LinearLength) + { + return false; + } + + ReadOnlySpan leftLengths = left.Lengths; + ReadOnlySpan leftLinearRankOrder = left.LinearRankOrder; + ReadOnlySpan leftStrides = left.Strides; + + ReadOnlySpan rightLengths = right.Lengths; + ReadOnlySpan rightLinearRankOrder = right.LinearRankOrder; + ReadOnlySpan rightStrides = right.Strides; + + for (int i = 0; i < rank; i++) + { + // We need to compare lengths and strides based on the linearRankOrder + // to ensure that two tensors representing the same memory, but where + // the shapes are logically, but not physically, transposed are considered + // equal. + + int leftRankIndex = leftLinearRankOrder[i]; + int rightRankIndex = rightLinearRankOrder[i]; + + if (leftLengths[leftRankIndex] != rightLengths[rightRankIndex]) + { + return false; + } + + if (leftStrides[leftRankIndex] != rightStrides[rightRankIndex]) + { + return false; + } + } + + return true; + } + + public static bool operator !=(in TensorShape left, in TensorShape right) => !(left == right); + + public nint AdjustToNextIndex(in TensorShape destinationShape, nint linearOffset, Span indexes) + { + Debug.Assert(indexes.Length >= Rank); + Debug.Assert(indexes.Length == destinationShape.Rank); + + ReadOnlySpan lengths = Lengths; + ReadOnlySpan strides = Strides; + + ReadOnlySpan destinationLengths = destinationShape.Lengths; + + for (int i = 0; i < strides.Length; i++) + { + int rankIndex = lengths.Length - (i + 1); + int destinationRankIndex = destinationShape.Lengths.Length - (i + 1); + + nint length = lengths[rankIndex]; + nint stride = strides[rankIndex]; + + nint index = ++indexes[destinationRankIndex]; + linearOffset += stride; + + // We can have a scenario such as lengths: [1], destinationLengths: [2] in + // which case we still need to keep incrementing the index but without + // adjusting the linearOffset + + if (index < destinationLengths[destinationRankIndex]) + { + if (index >= length) + { + // We should only be here if we were broadcast + Debug.Assert((length == 1) && (stride == 0)); + } + return linearOffset; + } + + indexes[destinationRankIndex] = 0; + linearOffset -= (stride * length); + } + + if (indexes.Length != Rank) + { + for (int i = strides.Length; i < destinationLengths.Length; i++) + { + int rankIndex = destinationLengths.Length - (i + 1); + + nint length = destinationLengths[rankIndex]; + // Strides are always 0 because we are broadcasting at this point in the loop. + + nint index = ++indexes[rankIndex]; + + if (index < length) + { + // For any indexes that exist in the destinationShape but not + // in the srcShape we will only increment them if all lower + // indexes were 0. This means we're starting over at the beginning + // of the srcShape and the linearOffset must be 0. + break; + } + + indexes[rankIndex] = 0; + } + } + return 0; + } + + public nint AdjustToPreviousIndex(in TensorShape destinationShape, nint linearOffset, Span indexes) + { + Debug.Assert(indexes.Length >= Rank); + Debug.Assert(indexes.Length == destinationShape.Rank); + + ReadOnlySpan lengths = Lengths; + ReadOnlySpan strides = Strides; + + ReadOnlySpan destinationLengths = destinationShape.Lengths; + + for (int i = 0; i < strides.Length; i++) + { + int rankIndex = lengths.Length - (i + 1); + int destinationRankIndex = destinationShape.Lengths.Length - (i + 1); + + nint length = lengths[rankIndex]; + nint stride = strides[rankIndex]; + + nint index = --indexes[destinationRankIndex]; + linearOffset -= stride; + + // We can have a scenario such as lengths: [1], destinationLengths: [2] in + // which case we still need to keep incrementing the index but without + // adjusting the linearOffset + + if (index >= 0)//destinationLengths[destinationRankIndex]) + { + if (index >= length) + { + // We should only be here if we were broadcast + Debug.Assert((length == 1) && (stride == 0)); + } + return linearOffset; + } + + indexes[destinationRankIndex] = lengths[rankIndex]; + linearOffset += (stride * length); + } + + if (indexes.Length != Rank) + { + for (int i = destinationLengths.Length - 1; i >= strides.Length; i--) + { + int rankIndex = destinationLengths.Length - (i + 1); + + nint length = destinationLengths[rankIndex]; + // Strides are always 0 because we are broadcasting at this point in the loop. + + nint index = ++indexes[rankIndex]; + + if (index < length) + { + // For any indexes that exist in the destinationShape but not + // in the srcShape we will only increment them if all lower + // indexes were 0. This means we're starting over at the beginning + // of the srcShape and the linearOffset must be 0. + break; + } + + indexes[rankIndex] = 0; + } + } + return 0; + } + + // Answer the question: Can shape2 turn into shape1 or vice-versa if allowBidirectional? + public static bool AreCompatible(in TensorShape shape1, in TensorShape shape2, bool allowBidirectional) + { + int rankDelta = shape1.Rank - shape2.Rank; + + if (rankDelta < 0) + { + if (!allowBidirectional) + { + return false; + } + + ref readonly TensorShape tmpShape = ref shape1; + shape1 = ref shape2; + shape2 = ref tmpShape; + + rankDelta = -rankDelta; + Debug.Assert(rankDelta > 0); + } + + // We need both to be empty if either is empty + + if (shape1.IsEmpty) + { + return shape2.IsEmpty; + } + else if (shape2.IsEmpty) + { + return false; + } + + // We need the lengths to be equal, length2 to be 1, or + // length1 to be 1 and be doing a bidirectional check. + + ReadOnlySpan lengths1 = shape1.Lengths[rankDelta..]; + ReadOnlySpan lengths2 = shape2.Lengths; + + for (int i = 0; i < lengths1.Length; i++) + { + nint length1 = lengths1[i]; + nint length2 = lengths2[i]; + + if (length1 == length2) + { + continue; + } + else if ((length1 == 1) && allowBidirectional) + { + continue; + } + else if (length2 == 1) + { + continue; + } + + return false; + } + + if (!allowBidirectional) + { + // When we aren't bidirectionally compatible, then we + // need to ensure that if stride1 is 0, then stride2 + // is also zero; otherwise we cannot safely operate. + + ReadOnlySpan strides1 = shape1.Strides[rankDelta..]; + ReadOnlySpan strides2 = shape2.Strides; + + for (int i = 0; i < strides1.Length; i++) + { + nint stride1 = strides1[i]; + nint stride2 = strides2[i]; - public bool IsEmpty => FlattenedLength == 0; + if ((stride1 == 0) && (stride2 != 0)) + { + return false; + } + } + } + return true; + } + + // Answer the question: Can shape2 turn into shape1Lengths + public static bool AreCompatible(in ReadOnlySpan shape1Lengths, in TensorShape shape2) + { + int rankDelta = shape1Lengths.Length - shape2.Rank; + + if (rankDelta < 0) + { + return false; + } + + // We need both to be empty if either is empty + + if (shape1Lengths.IsEmpty) + { + return shape2.IsEmpty; + } + else if (shape2.IsEmpty) + { + return false; + } + + // We need the lengths to be equal, length2 to be 1, or + // length1 to be 1 and be doing a bidirectional check. + + ReadOnlySpan lengths1 = shape1Lengths[rankDelta..]; + ReadOnlySpan lengths2 = shape2.Lengths; + + for (int i = 0; i < lengths1.Length; i++) + { + nint length1 = lengths1[i]; + nint length2 = lengths2[i]; + + if (length1 == length2) + { + continue; + } + else if (length2 == 1) + { + continue; + } + + return false; + } + + return true; + } + + public static bool AreLengthsTheSame(in TensorShape shape1, in TensorShape shape2) + { + return AreLengthsTheSame(shape1.Lengths, shape2.Lengths); + } + + public static bool AreLengthsTheSame(ReadOnlySpan lengths1, ReadOnlySpan lengths2) + { + return lengths1.SequenceEqual(lengths2); + } + + public static TensorShape Create(Array? array) + { + if (array is not null) + { + nint linearLength = (nint)array.LongLength; + int rank = array.Rank; + + nint[]? lengthsArray = null; + InlineBuffer lengthsBuffer; + scoped Span lengths; + + if (rank > MaxInlineRank) + { + lengthsArray = ArrayPool.Shared.Rent(rank); + lengths = lengthsArray.AsSpan(0, rank); + } + else + { + Unsafe.SkipInit(out lengthsBuffer); + lengths = ((Span)lengthsBuffer)[..rank]; + } + + for (int i = 0; i < rank; i++) + { + lengths[i] = array.GetLength(i); + } + + TensorShape result = new TensorShape( + linearLength, + lengths, + strides: [], + linearRankOrder: [] + ); + + if (lengthsArray is not null) + { + ArrayPool.Shared.Return(lengthsArray); + } + return result; + } + return default; + } + + public static TensorShape Create(Array? array, scoped ReadOnlySpan start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, out nint linearOffset) + { + nint computedOffset = 0; + + nint[]? intermediateLengthsArray = null; + InlineBuffer intermediateLengthsBuffer; + scoped Span intermediateLengths = default; + + if (array is not null) + { + int rank = array.Rank; + + if ((start.Length != 0) && (start.Length != rank)) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + nint linearLength = (nint)array.LongLength; + + if (lengths.Length == 0) + { + // When no lengths are specified we need to retrieve them from the array + // since that has the expected shape. We don't need to validate the strides + // however as that will be done by the TensorShape constructor. + + if (rank > MaxInlineRank) + { + intermediateLengthsArray = ArrayPool.Shared.Rent(rank); + intermediateLengths = intermediateLengthsArray.AsSpan(0, rank); + } + else + { + Unsafe.SkipInit(out intermediateLengthsBuffer); + intermediateLengths = ((Span)intermediateLengthsBuffer)[..rank]; + } + + for (int i = 0; i < rank; i++) + { + intermediateLengths[i] = array.GetLength(i); + } + + lengths = intermediateLengths; + } + + if (start.Length != 0) + { + // In the case a starting index is specified, we need to compute the linear + // index that is the actual starting position. Additionally, if no lengths + // were specified we want to adjust the lengths computed from the array to + // ensure they remain correct. However, we don't validate or adjust the lengths + // if they were user specified as we expect them to already be correct. This + // is because we allow users to do a "reshape" as part of construction and so + // the lengths and strides can mismatch what the underlying multidimensional + // array may have itself. + + nint stride = 1; + + for (int i = 0; i < start.Length; i++) + { + int index = start.Length - (i + 1); + int offset = start[index]; + int length = array.GetLength(index); + + if ((offset < 0) || (offset > length)) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + computedOffset += (offset * stride); + stride *= length; + + if (intermediateLengths.Length != 0) + { + intermediateLengths[index] -= offset; + } + } + } + + if ((computedOffset < 0) || (computedOffset > linearLength)) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + TensorShape result = new TensorShape(linearLength - computedOffset, lengths, strides, linearRankOrder: []); + + if (intermediateLengthsArray is not null) + { + ArrayPool.Shared.Return(intermediateLengthsArray); + } + linearOffset = computedOffset; + return result; + } + else if (start.Length != 0) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + if ((lengths.Length != 0) || (strides.Length != 0)) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + linearOffset = computedOffset; + return default; + } + + public static TensorShape Create(scoped ReadOnlySpan lengths) + => new TensorShape(linearLength: -1, lengths, strides: [], linearRankOrder: []); + + public static TensorShape Create(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => new TensorShape(linearLength: -1, lengths, strides, linearRankOrder: []); + + public static TensorShape Create(T[]? array) + { + if (array is not null) + { + int linearLength = array.Length; + + return new TensorShape( + flattenedLength: linearLength, + linearLength, + lengths: [linearLength], + strides: [1], + linearRankOrder: [0], + rank: 1, + TensorFlags.IsContiguousAndDense + ); + } + return default; + } + + public static TensorShape Create(T[]? array, scoped ReadOnlySpan lengths) + { + if (array is not null) + { + int linearLength = array.Length; + return new TensorShape(linearLength, lengths, strides: [], linearRankOrder: []); + } + + if (lengths.Length != 0) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + return default; + } + + public static TensorShape Create(T[]? array, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + if (array is not null) + { + int linearLength = array.Length; + return new TensorShape(linearLength, lengths, strides, linearRankOrder: []); + } + + if ((lengths.Length != 0) || (strides.Length != 0)) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + return default; + } + + public static TensorShape Create(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + if (array is not null) + { + int linearLength = array.Length; + + if ((start < 0) || (start > linearLength)) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + linearLength -= start; + return new TensorShape(linearLength, lengths, strides, linearRankOrder: []); + } + else if (start != 0) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + if ((lengths.Length != 0) || (strides.Length != 0)) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + return default; + } + + public static TensorShape Create(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, scoped ReadOnlySpan linearRankOrder) + { + if (array is not null) + { + int linearLength = array.Length; + + if ((start < 0) || (start > linearLength)) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + linearLength -= start; + return new TensorShape(linearLength, lengths, strides, linearRankOrder); + } + else if (start != 0) + { + ThrowHelper.ThrowArgument_StartIndexOutOfBounds(); + } + + if ((lengths.Length != 0) || (strides.Length != 0)) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + return default; + } + + public static TensorShape Create(ref T reference, nint linearLength) + { + if (!Unsafe.IsNullRef(ref reference)) + { + return new TensorShape( + flattenedLength: linearLength, + linearLength, + lengths: [linearLength], + strides: [1], + linearRankOrder: [0], + rank: 1, + TensorFlags.IsContiguousAndDense + ); + } + else if (linearLength != 0) + { + ThrowHelper.ThrowArgument_LengthIsNonZeroForNullReference(); + } + return default; + } + + public static TensorShape Create(ref T reference, nint linearLength, scoped ReadOnlySpan lengths) + => Create(ref reference, linearLength, lengths, strides: [], linearRankOrder: []); + + public static TensorShape Create(ref T reference, nint linearLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => Create(ref reference, linearLength, lengths, strides, linearRankOrder: []); + + public static TensorShape Create(ref T reference, nint linearLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, scoped ReadOnlySpan linearRankOrder) + { + if (!Unsafe.IsNullRef(ref reference)) + { + return new TensorShape(linearLength, lengths, strides, linearRankOrder); + } + else if (linearLength != 0) + { + ThrowHelper.ThrowArgument_LengthIsNonZeroForNullReference(); + } + + if ((lengths.Length != 0) || (strides.Length != 0)) + { + ThrowHelper.ThrowArgument_InvalidTensorShape(); + } + return default; + } + + public static unsafe TensorShape Create(T* address, nint linearLength) + => Create(ref Unsafe.AsRef(address), linearLength); + + public static unsafe TensorShape Create(T* address, nint linearLength, scoped ReadOnlySpan lengths) + => Create(ref Unsafe.AsRef(address), linearLength, lengths, strides: []); + + public static unsafe TensorShape Create(T* address, nint linearLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + => Create(ref Unsafe.AsRef(address), linearLength, lengths, strides); + + public override bool Equals([NotNullWhen(true)] object? obj) + => (obj is TensorShape other) && (this == other); + + public bool Equals(TensorShape other) => (this == other); + + public override int GetHashCode() => base.GetHashCode(); + + [Conditional("DEBUG")] + private void ValidateState() + { + Debug.Assert(IsContiguousAndDense == (FlattenedLength == LinearLength)); + Debug.Assert(IsBroadcast == Strides.Contains(0)); + } + + public nint GetLinearOffset(ReadOnlySpan state) + where TGetOffsetAndLength : IGetOffsetAndLength + { + ReadOnlySpan lengths = Lengths; + ReadOnlySpan strides = Strides; + + if ((state.Length != lengths.Length) || + (state.Length != strides.Length)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(); + } + + nint linearOffset = 0; + + for (int i = 0; i < state.Length; i++) + { + nint length = lengths[i]; + nint stride = strides[i]; + + nint offset = TGetOffsetAndLength.GetOffset(state, i, length); + linearOffset += (offset * stride); + } + + return linearOffset; + } + + public TensorShape Slice(ReadOnlySpan state, out nint linearOffset) + where TGetOffsetAndLength : IGetOffsetAndLength + { + int rank = Rank; + + nint[]? intermediateLengthsArray = null; + InlineBuffer intermediateLengthsBuffer; + scoped Span intermediateLengths; + + nint[]? intermediateStridesArray = null; + InlineBuffer intermediateStridesBuffer; + scoped Span intermediateStrides; + + if (rank > MaxInlineRank) + { + intermediateLengthsArray = ArrayPool.Shared.Rent(rank); + intermediateLengths = intermediateLengthsArray.AsSpan(0, rank); + + intermediateStridesArray = ArrayPool.Shared.Rent(rank); + intermediateStrides = intermediateStridesArray.AsSpan(0, rank); + } + else + { + Unsafe.SkipInit(out intermediateLengthsBuffer); + intermediateLengths = ((Span)intermediateLengthsBuffer)[..rank]; + + Unsafe.SkipInit(out intermediateStridesBuffer); + intermediateStrides = ((Span)intermediateStridesBuffer)[..rank]; + } + + ReadOnlySpan previousLengths = Lengths; + ReadOnlySpan previousStrides = Strides; + ReadOnlySpan linearRankOrder = LinearRankOrder; + + if ((state.Length != previousLengths.Length) || + (state.Length != linearRankOrder.Length) || + (state.Length != previousStrides.Length)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(); + } + + // The previous strides and rank order persist in the new shape with + // only the lengths having changed based on the new starting index. + // + // Accordingly, we can also simplify some of the checks as we can + // assume that the previousShape is already valid and the new shape + // will strictly be the same size or smaller. + + TensorFlags flags = TensorFlags.None; + + nint flattenedLength = 1; + nint maximumLinearIndex = 0; + + nint computedOffset = 0; + + for (int i = 0; i < linearRankOrder.Length; i++) + { + int rankIndex = linearRankOrder.Length - (i + 1); + int linearRankIndex = linearRankOrder[rankIndex]; + + nint previousLength = previousLengths[linearRankIndex]; + nint previousStride = previousStrides[linearRankIndex]; + + (nint offset, nint length) = TGetOffsetAndLength.GetOffsetAndLength(state, linearRankIndex, previousLength); + nint stride = (length > 1) ? previousStride : 0; + + if (stride != 0) + { + maximumLinearIndex += ((length - 1) * stride); + } + else + { + flags |= TensorFlags.IsBroadcast; + } + + intermediateLengths[linearRankIndex] = length; + intermediateStrides[linearRankIndex] = stride; + + computedOffset += (offset * previousStride); + flattenedLength *= length; + } + + // We've computed the maximum linear index based on the strides + // so the minimum length must be one higher than that value. + + nint minimumLinearLength = (flattenedLength != 0) ? (maximumLinearIndex + 1) : flattenedLength; + Debug.Assert(minimumLinearLength <= _linearLength); + + if (flattenedLength == minimumLinearLength) + { + flags |= TensorFlags.IsContiguousAndDense; + } + + TensorShape result = new TensorShape( + flattenedLength, + minimumLinearLength, + intermediateLengths, + intermediateStrides, + linearRankOrder, + rank, + flags + ); + + if (intermediateLengthsArray is not null) + { + ArrayPool.Shared.Return(intermediateLengthsArray); + } + + if (intermediateStridesArray is not null) + { + ArrayPool.Shared.Return(intermediateStridesArray); + } + + Debug.Assert(computedOffset == GetLinearOffset(state)); + linearOffset = computedOffset; + + return result; + } + + public interface IGetOffsetAndLength + { + static abstract nint GetOffset(ReadOnlySpan state, int rankIndex, nint previousLength); + static abstract (nint Offset, nint Length) GetOffsetAndLength(ReadOnlySpan state, int rankIndex, nint previousLength); + } + + [InlineArray(MaxInlineRank)] + public struct InlineBuffer + { + public T e0; + } + + public readonly struct GetOffsetAndLengthForNInt : IGetOffsetAndLength + { + public static nint GetOffset(ReadOnlySpan indexes, int rankIndex, nint previousLength) + { + nint offset = indexes[rankIndex]; + + if ((offset < 0) || (offset >= previousLength)) + { + ThrowHelper.ThrowIndexOutOfRangeException(); + } + return offset; + } + + public static (nint Offset, nint Length) GetOffsetAndLength(ReadOnlySpan indexes, int rankIndex, nint previousLength) + { + nint offset = GetOffset(indexes, rankIndex, previousLength); + return (offset, previousLength - offset); + } + } + + public readonly struct GetOffsetAndLengthForNIndex : IGetOffsetAndLength + { + public static nint GetOffset(ReadOnlySpan indexes, int rankIndex, nint previousLength) + { + nint offset = indexes[rankIndex].GetOffset(previousLength); + + if ((offset < 0) || (offset >= previousLength)) + { + ThrowHelper.ThrowIndexOutOfRangeException(); + } + return offset; + } + + public static (nint Offset, nint Length) GetOffsetAndLength(ReadOnlySpan indexes, int rankIndex, nint previousLength) + { + nint offset = GetOffset(indexes, rankIndex, previousLength); + return (offset, previousLength - offset); + } + } + + public readonly struct GetOffsetAndLengthForNRange : IGetOffsetAndLength + { + public static nint GetOffset(ReadOnlySpan ranges, int rankIndex, nint previousLength) + { + return ranges[rankIndex].Start.GetOffset(previousLength); + } + + public static (nint Offset, nint Length) GetOffsetAndLength(ReadOnlySpan ranges, int rankIndex, nint previousLength) + { + return ranges[rankIndex].GetOffsetAndLength(previousLength); + } + } } } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs index a71eaebac3822a..093a922f2f3789 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs @@ -2,14 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; -using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; -#pragma warning disable 0809 //warning CS0809: Obsolete member 'TensorSpan.Equals(object)' overrides non-obsolete member 'object.Equals(object)' +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member namespace System.Numerics.Tensors { @@ -22,489 +23,271 @@ namespace System.Numerics.Tensors [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] public readonly ref struct TensorSpan { - /// A byref or a native ptr. - internal readonly ref T _reference; - internal readonly TensorShape _shape; + /// + public static TensorSpan Empty => default; + internal readonly TensorShape _shape; + internal readonly ref T _reference; - /// - /// Creates a new span over the entirety of the target array. - /// - /// The target array. - /// Returns default when is null. + /// /// is covariant and its type is not exactly T[]. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TensorSpan(T[]? array) : this(array, 0, [array?.Length ?? 0], []) + public TensorSpan(T[]? array) { + ThrowHelper.ThrowIfArrayTypeMismatch(array); + + _shape = TensorShape.Create(array); + _reference = ref (array is not null) + ? ref MemoryMarshal.GetArrayDataReference(array) + : ref Unsafe.NullRef(); } - /// - /// Creates a new span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// - /// The target array. - /// The index at which to begin the span. - /// The lengths of the dimensions. If default is provided, it's assumed to have one dimension with a length equal to the length of the data. - /// The strides of each dimension. If default or span of length 0 is provided, then strides will be automatically calculated. - /// Returns default when is null. + /// /// is covariant and its type is not exactly T[]. - /// - /// The specified or end index is not in the range (<0 or >FlattenedLength). - /// - public TensorSpan(T[]? array, Index startIndex, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) - : this(array, startIndex.GetOffset(array?.Length ?? 0), lengths, strides) + public TensorSpan(T[]? array, scoped ReadOnlySpan lengths) { + ThrowHelper.ThrowIfArrayTypeMismatch(array); + + _shape = TensorShape.Create(array, lengths); + _reference = ref (array is not null) + ? ref MemoryMarshal.GetArrayDataReference(array) + : ref Unsafe.NullRef(); } - /// - /// Creates a new span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// - /// The target array. - /// The index at which to begin the span. - /// The lengths of the dimensions. If default is provided, it's assumed to have one dimension with a length equal to the length of the data. - /// The strides of each dimension. If default or span of length 0 is provided, then strides will be automatically calculated. - /// Returns default when is null. + /// /// is covariant and its type is not exactly T[]. - /// - /// The specified or end index is not in the range (<0 or >FlattenedLength). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TensorSpan(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + public TensorSpan(T[]? array, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) { - if (lengths.IsEmpty && array != null) - lengths = [array.Length]; + ThrowHelper.ThrowIfArrayTypeMismatch(array); - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - if (array == null) - { - if (start != 0 || linearLength != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - this = default; - return; // returns default - } + _shape = TensorShape.Create(array, lengths, strides); + _reference = ref (array is not null) + ? ref MemoryMarshal.GetArrayDataReference(array) + : ref Unsafe.NullRef(); + } - if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException(); + /// + /// is covariant and its type is not exactly T[]. + public TensorSpan(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + ThrowHelper.ThrowIfArrayTypeMismatch(array); - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); + _shape = TensorShape.Create(array, start, lengths, strides); + _reference = ref (array is not null) + ? ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (uint)start) + : ref Unsafe.NullRef(); + } - if (Environment.Is64BitProcess) - { - // See comment in Span.Slice for how this works. - if ((ulong)(uint)start + (ulong)(uint)maxElements >= (ulong)(uint)array.Length && array.Length != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - } - else - { - if (((uint)start > (uint)array.Length || (uint)maxElements >= (uint)(array.Length - start)) && array.Length != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - } + /// + public TensorSpan(Span span) + { + ref T reference = ref MemoryMarshal.GetReference(span); + _shape = TensorShape.Create(ref reference, span.Length); + _reference = ref reference; + } - _shape = new TensorShape(array.Length - start, lengths, strides); - _reference = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (nint)(uint)start /* force zero-extension */); + /// + public TensorSpan(Span span, scoped ReadOnlySpan lengths) + { + ref T reference = ref MemoryMarshal.GetReference(span); + _shape = TensorShape.Create(ref reference, span.Length, lengths); + _reference = ref reference; } - /// - /// Creates a new over the provided . The new will - /// have a rank of 1 and a length equal to the length of the provided . - /// - /// The target span. - public TensorSpan(Span span) : this(span, [span.Length], []) { } - - /// - /// Creates a new over the provided using the specified lengths and strides. - /// - /// The target span. - /// The lengths of each dimension. - /// The strides for each dimension. The strides will be automatically calculated if not provided. + /// public TensorSpan(Span span, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) { - if (lengths.IsEmpty) - lengths = [span.Length]; - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (span.IsEmpty ? maxElements != 0 : maxElements >= span.Length) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - - _shape = new TensorShape(span.Length, lengths, strides); - _reference = ref MemoryMarshal.GetReference(span); + ref T reference = ref MemoryMarshal.GetReference(span); + _shape = TensorShape.Create(ref reference, span.Length, lengths, strides); + _reference = ref reference; } - /// - /// Creates a new over the provided . The new will - /// have a rank of 1 and a length equal to the length of the provided . - /// - /// The target array. - public TensorSpan(Array? array) : - this(array, - ReadOnlySpan.Empty, - array == null ? - [0] : - TensorSpanHelpers.FillLengths(array.Rank <= TensorShape.MaxInlineRank ? stackalloc nint[array.Rank] : new nint[array.Rank], array), - []) + /// + /// is covariant and its type is not exactly T[]. + public TensorSpan(Array? array) { + ThrowHelper.ThrowIfArrayTypeMismatch(array); + + _shape = TensorShape.Create(array); + _reference = ref (array is not null) + ? ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)) + : ref Unsafe.NullRef(); } - /// - /// Creates a new over the provided using the specified start offsets, lengths, and strides. - /// - /// The target array. - /// The starting offset for each dimension. - /// The lengths of each dimension. - /// The strides for each dimension. The strides will be automatically calculated if not provided. + /// + /// is covariant and its type is not exactly T[]. public TensorSpan(Array? array, scoped ReadOnlySpan start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) { - if (lengths.IsEmpty && array != null) - { - lengths = TensorSpanHelpers.FillLengths( - array.Rank < TensorShape.MaxInlineRank ? stackalloc nint[array.Rank] : new nint[array.Rank], - array); - } - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - - if (array == null) - { - if (!start.IsEmpty || linearLength != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - this = default; - return; // returns default - } - if (array.GetType().GetElementType() != typeof(T)) - ThrowHelper.ThrowArrayTypeMismatchException(); - - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); + ThrowHelper.ThrowIfArrayTypeMismatch(array); - nint startOffset = TensorSpanHelpers.ComputeStartOffsetSystemArray(array, start); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (Environment.Is64BitProcess) - { - // See comment in Span.Slice for how this works. - if ((ulong)(uint)startOffset + (ulong)(uint)maxElements >= (ulong)(uint)array.Length && array.Length != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - else - { - if (((uint)startOffset > (uint)array.Length || (uint)maxElements >= (uint)(array.Length - startOffset)) && array.Length != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - - _shape = new TensorShape(array.Length - startOffset, lengths, strides); - _reference = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), (nint)(uint)startOffset /* force zero-extension */); + _shape = TensorShape.Create(array, start, lengths, strides, out nint linearOffset); + _reference = ref (array is not null) + ? ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), linearOffset) + : ref Unsafe.NullRef(); } - /// - /// Creates a new over the provided using the specified start offsets, lengths, and strides. - /// - /// The target array. - /// The starting offset for each dimension. - /// The lengths of each dimension. - /// The strides for each dimension. The strides will be automatically calculated if not provided. - public TensorSpan(Array? array, scoped ReadOnlySpan startIndex, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + /// + [CLSCompliant(false)] + public unsafe TensorSpan(T* data, nint dataLength) { - if (lengths.IsEmpty && array != null) - { - lengths = TensorSpanHelpers.FillLengths( - array.Rank <= TensorShape.MaxInlineRank ? stackalloc nint[array.Rank] : new nint[array.Rank], - array); - } - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); - if (array == null) - { - if (!startIndex.IsEmpty || linearLength != 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - this = default; - return; // returns default - } - if (array.GetType().GetElementType() != typeof(T)) - ThrowHelper.ThrowArrayTypeMismatchException(); - - nint startOffset = TensorSpanHelpers.ComputeStartOffsetSystemArray(array, startIndex); - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (Environment.Is64BitProcess) - { - // See comment in Span.Slice for how this works. - if ((ulong)(uint)startOffset + (ulong)(uint)maxElements > (ulong)(uint)array.Length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - else - { - if ((uint)startOffset > (uint)array.Length || (uint)maxElements >= (uint)(array.Length - startOffset)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - } - - _shape = new TensorShape(array.Length, lengths, strides); - _reference = ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), (nint)(uint)startOffset /* force zero-extension */); + _shape = TensorShape.Create(data, dataLength); + _reference = ref Unsafe.AsRef(data); } - /// - /// Creates a new span over the target unmanaged buffer. - /// - /// An unmanaged data that points to memory. - /// The number of elements the unmanaged memory can hold. - /// - /// This constructor is quite dangerous, because the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// + /// [CLSCompliant(false)] - public unsafe TensorSpan(T* data, nint dataLength) : this(data, dataLength, [dataLength], []) { } - - /// - /// Creates a new span over the target unmanaged buffer. - /// - /// An unmanaged data that points to memory. - /// The number of elements the unmanaged memory can hold. - /// The lengths of the dimensions. If default is provided, it's assumed to have one dimension with a length equal to the length of the data. - /// The lengths of the strides. If nothing is provided, it figures out the default stride configuration. - /// - /// is a reference type or contains pointers and hence cannot be stored in unmanaged memory. - /// - /// - /// The specified length is negative. - /// - /// - /// This constructor is quite dangerous, because the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// + public unsafe TensorSpan(T* data, nint dataLength, scoped ReadOnlySpan lengths) + { + _shape = TensorShape.Create(data, dataLength, lengths); + _reference = ref Unsafe.AsRef(data); + } + + /// [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe TensorSpan(T* data, nint dataLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) { - if (dataLength < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - - if (lengths.IsEmpty) - lengths = [dataLength]; - - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); + _shape = TensorShape.Create(data, dataLength, lengths, strides); + _reference = ref Unsafe.AsRef(data); + } - strides = strides.IsEmpty ? (ReadOnlySpan)TensorSpanHelpers.CalculateStrides(lengths, linearLength) : strides; - TensorSpanHelpers.ValidateStrides(strides, lengths); + internal TensorSpan(ref T data, nint dataLength) + { + _shape = TensorShape.Create(ref data, dataLength); + _reference = ref data; + } - nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(strides, lengths); - if (maxElements >= dataLength && dataLength != 0) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); + internal TensorSpan(ref T data, nint dataLength, scoped ReadOnlySpan lengths) + { + _shape = TensorShape.Create(ref data, dataLength, lengths); + _reference = ref data; + } - _shape = new TensorShape(dataLength, lengths, strides); - _reference = ref *data; + internal TensorSpan(ref T data, nint dataLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(ref data, dataLength, lengths, strides); + _reference = ref data; } - // Constructor for internal use only. It is not safe to expose publicly. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal TensorSpan(ref T reference, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, nint memoryLength) + internal TensorSpan(ref T data, nint dataLength, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, scoped ReadOnlySpan linearRankOrder) { - nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths); + _shape = TensorShape.Create(ref data, dataLength, lengths, strides, linearRankOrder); + _reference = ref data; + } - _shape = new TensorShape(memoryLength, lengths, strides); + internal TensorSpan(ref T reference, scoped in TensorShape shape) + { _reference = ref reference; + _shape = shape; } - /// - /// Returns a reference to specified element of the TensorSpan. - /// - /// - /// - /// - /// Any index is less than 0 or greater than or equal to FlattenedLength. - /// + /// public ref T this[params scoped ReadOnlySpan indexes] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if (indexes.Length != Rank) - ThrowHelper.ThrowIndexOutOfRangeException(); - - nint index = TensorSpanHelpers.ComputeLinearIndex(indexes, Strides, Lengths); - if (index >= _shape._memoryLength || index < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return ref Unsafe.Add(ref _reference, index /* force zero-extension */); - } + get => ref Unsafe.Add(ref _reference, _shape.GetLinearOffset(indexes)); } - /// - /// Returns a reference to specified element of the TensorSpan. - /// - /// - /// - /// - /// Any index is less than 0 or greater than or equal to FlattenedLength. - /// + /// public ref T this[params scoped ReadOnlySpan indexes] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - - if (indexes.Length != Rank) - ThrowHelper.ThrowIndexOutOfRangeException(); - - nint index = TensorSpanHelpers.ComputeLinearIndex(indexes, Strides, Lengths); - if (index >= _shape._memoryLength || index < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return ref Unsafe.Add(ref _reference, index /* force zero-extension */); - } + get => ref Unsafe.Add(ref _reference, _shape.GetLinearOffset(indexes)); } - /// - /// Returns a slice of the TensorSpan. - /// - /// - /// - /// - /// Any index is less than 0 or greater than or equal to FlattenedLength. - /// + /// public TensorSpan this[params scoped ReadOnlySpan ranges] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return Slice(ranges); - } - set - { - value.CopyTo(this[ranges]); - } + get => Slice(ranges); + set => value.CopyTo(Slice(ranges)); } - /// - /// The number of items in the span. - /// + /// public nint FlattenedLength => _shape.FlattenedLength; - /// - /// Gets a value indicating whether this is empty. - /// - /// if this span is empty; otherwise, . + internal bool IsContiguousAndDense => _shape.IsContiguousAndDense; + + /// public bool IsEmpty => _shape.IsEmpty; - /// - /// Gets the length of each dimension in this . - /// + /// [UnscopedRef] public ReadOnlySpan Lengths => _shape.Lengths; - /// - /// Gets the rank, aka the number of dimensions, of this . - /// + /// public int Rank => Lengths.Length; - /// - /// Gets the strides of this - /// + /// [UnscopedRef] public ReadOnlySpan Strides => _shape.Strides; - /// - /// Compares two spans and returns false if left and right point at the same memory and have the same length. - /// This operator does *not* check to see if the *contents* are equal. - /// - public static bool operator !=(TensorSpan left, TensorSpan right) => !(left == right); - - /// - /// Compares two spans and returns true if left and right point at the same memory and have the same length. - /// This operator does *not* check to see if the *contents* are equal. - /// - public static bool operator ==(TensorSpan left, TensorSpan right) => - left._shape.FlattenedLength == right._shape.FlattenedLength && - left.Rank == right.Rank && - left._shape.Lengths.SequenceEqual(right._shape.Lengths) && - left._shape.Strides.SequenceEqual(right._shape.Strides) && - Unsafe.AreSame(ref left._reference, ref right._reference); - - /// - /// This method is not supported as spans cannot be boxed. To compare two spans, use operator ==. - /// - /// - /// In all cases. - /// - [Obsolete("Equals() on TensorSpan will always throw an exception. Use the equality operator instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => - throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan); + /// + public static bool operator ==(in TensorSpan left, in TensorSpan right) + => Unsafe.AreSame(ref left._reference, ref right._reference) + && left._shape == right._shape; - /// - /// This method is not supported as spans cannot be boxed. - /// - /// - /// In all cases. - /// - [Obsolete("GetHashCode() on TensorSpan will always throw an exception.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => - throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan); + /// + public static bool operator !=(in TensorSpan left, in TensorSpan right) => !(left == right); - /// - /// Returns an empty - /// - public static TensorSpan Empty => default; + /// Defines an implicit conversion of an array to a tensor span. + /// The array to convert to a tensor span. + /// The tensor span that corresponds to . + public static implicit operator TensorSpan(T[]? array) => new TensorSpan(array); - /// Gets an enumerator for this span. - public Enumerator GetEnumerator() => new Enumerator(this); + /// Defines an implicit conversion of a tensor to a readonly tensor span. + /// The tensor to convert to a readonly tensor span. + /// The tensor that corresponds to . + public static implicit operator ReadOnlyTensorSpan(scoped in TensorSpan tensor) => + new ReadOnlyTensorSpan(ref tensor._reference, tensor._shape); - /// Enumerates the elements of a . - public ref struct Enumerator - { - /// The span being enumerated. - private readonly TensorSpan _span; - /// The current index that the enumerator is on. - private Span _curIndexes; - /// The total item count. - private nint _items; - - /// Initialize the enumerator. - /// The span to enumerate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(TensorSpan span) - { - _span = span; - _items = -1; - _curIndexes = new nint[_span.Rank]; + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan() => new ReadOnlyTensorSpan(ref _reference, in _shape); - _curIndexes[_span.Rank - 1] = -1; - } + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndexes) => AsReadOnlyTensorSpan().Slice(startIndexes); - /// Advances the enumerator to the next element of the span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - TensorSpanHelpers.AdjustIndexes(_span.Rank - 1, 1, _curIndexes, _span.Lengths); + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndexes) => AsReadOnlyTensorSpan().Slice(startIndexes); + + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan ranges) => AsReadOnlyTensorSpan().Slice(ranges); - if (_items < _span.FlattenedLength) - _items++; + /// + public void Clear() => TensorOperation.Invoke, T>(this); - return _items < _span.FlattenedLength; + /// + public void CopyTo(scoped in TensorSpan destination) + { + if (!TryCopyTo(destination)) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); } + } - /// Gets the element at the current position of the enumerator. - public ref T Current + /// + [Obsolete("Equals() on TensorSpan will always throw an exception. Use the equality operator instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => + throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan); + + /// + public void Fill(T value) => TensorOperation.Invoke, T, T>(this, value); + + /// + public void FlattenTo(scoped Span destination) + { + if (!TryFlattenTo(destination)) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _span[_curIndexes]; + ThrowHelper.ThrowArgument_DestinationTooShort(); } } - /// - /// Returns a reference to the 0th element of the TensorSpan. If the TensorSpan is empty, returns null reference. - /// It can be used for pinning and is required to support the use of span within a fixed statement. - /// + /// Gets an enumerator for the tensor span. + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + [Obsolete("GetHashCode() on TensorSpan will always throw an exception.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => + throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan); + + /// [EditorBrowsable(EditorBrowsableState.Never)] public ref T GetPinnableReference() { @@ -514,320 +297,108 @@ public ref T GetPinnableReference() return ref ret; } - /// - /// Clears the contents of this span. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - scoped Span curIndexes; - nint[]? curIndexesArray; - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray.AsSpan(0, Rank); - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[Rank]; - } - curIndexes.Clear(); - - nint clearedValues = 0; - while (clearedValues < _shape.FlattenedLength) - { - TensorSpanHelpers.Clear(ref Unsafe.Add(ref _reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, Strides, Lengths)), (nuint)Lengths[Rank - 1]); - TensorSpanHelpers.AdjustIndexes(Rank - 2, 1, curIndexes, _shape.Lengths); - clearedValues += Lengths[Rank - 1]; - } - } - - /// - /// Fills the contents of this span with the given value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Fill(T value) + /// + public TensorSpan Slice(params scoped ReadOnlySpan startIndexes) { - MemoryMarshal.CreateSpan(ref _reference, (int)_shape._memoryLength).Fill(value); + TensorShape shape = _shape.Slice(startIndexes, out nint linearOffset); + return new TensorSpan( + ref Unsafe.Add(ref _reference, linearOffset), + shape + ); } - /// - /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values are in - /// a temporary location before the destination is overwritten. - /// - /// The span to copy items into. - /// - /// The destination TensorSpan is shorter than the source Span. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(scoped TensorSpan destination) + /// + public TensorSpan Slice(params scoped ReadOnlySpan startIndexes) { - // Using "if (!TryCopyTo(...))" results in two branches: one for the length - // check, and one for the result of TryCopyTo. Since these checks are equivalent, - // we can optimize by performing the check once ourselves then calling Memmove directly. - if (TensorHelpers.IsBroadcastableTo(Lengths, destination.Lengths)) - { - scoped Span curIndexes; - nint[]? curIndexesArray; - - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(destination.Rank); - curIndexes = curIndexesArray.AsSpan(0, destination.Rank); - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[destination.Rank]; - } - curIndexes.Clear(); - - nint copiedValues = 0; - nint[] tempLengths = Tensor.GetSmallestBroadcastableLengths(Lengths, destination.Lengths); - - TensorSpan destinationSlice = destination.Slice(tempLengths); - ReadOnlyTensorSpan srcSlice = Tensor.LazyBroadcast(this, tempLengths); - nint copyLength = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Lengths[^1] : 1; - int indexToAdjust = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Rank - 2 : srcSlice.Rank - 1; - - while (copiedValues < destination.FlattenedLength) - { - TensorSpanHelpers.Memmove(ref Unsafe.Add(ref destinationSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, destinationSlice.Strides, destinationSlice.Lengths)), ref Unsafe.Add(ref srcSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, srcSlice.Strides, srcSlice.Lengths)), copyLength); - TensorSpanHelpers.AdjustIndexes(indexToAdjust, 1, curIndexes, tempLengths); - copiedValues += copyLength; - } - Debug.Assert(copiedValues == destination.FlattenedLength, "Didn't copy the right amount to the array."); - - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); - } - else - { - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - } + TensorShape shape = _shape.Slice(startIndexes, out nint linearOffset); + return new TensorSpan( + ref Unsafe.Add(ref _reference, linearOffset), + shape + ); } - /// - /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values are in - /// a temporary location before the destination is overwritten. - /// - /// The span to copy items into. - /// If the destination span is shorter than the source span, this method - /// return false and no data is written to the destination. - public bool TryCopyTo(scoped TensorSpan destination) + /// + public TensorSpan Slice(params scoped ReadOnlySpan ranges) { - bool retVal = false; - - if (TensorHelpers.IsBroadcastableTo(Lengths, destination.Lengths)) - { - scoped Span curIndexes; - nint[]? curIndexesArray; - - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(destination.Rank); - curIndexes = curIndexesArray.AsSpan(0, destination.Rank); - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[destination.Rank]; - } - curIndexes.Clear(); - - nint copiedValues = 0; - nint[] tempLengths = Tensor.GetSmallestBroadcastableLengths(Lengths, destination.Lengths); - - TensorSpan destinationSlice = destination.Slice(tempLengths); - ReadOnlyTensorSpan srcSlice = Tensor.LazyBroadcast(this, tempLengths); - nint copyLength = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Lengths[^1] : 1; - int indexToAdjust = srcSlice.Strides[^1] == 1 && TensorHelpers.IsContiguousAndDense(srcSlice) ? srcSlice.Rank - 2 : srcSlice.Rank - 1; - - while (copiedValues < destination.FlattenedLength) - { - TensorSpanHelpers.Memmove(ref Unsafe.Add(ref destinationSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, destinationSlice.Strides, destinationSlice.Lengths)), ref Unsafe.Add(ref srcSlice._reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, srcSlice.Strides, srcSlice.Lengths)), copyLength); - TensorSpanHelpers.AdjustIndexes(indexToAdjust, 1, curIndexes, tempLengths); - copiedValues += copyLength; - } - Debug.Assert(copiedValues == destination.FlattenedLength, "Didn't copy the right amount to the array."); - retVal = true; - - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); - } - return retVal; + TensorShape shape = _shape.Slice(ranges, out nint linearOffset); + return new TensorSpan( + ref Unsafe.Add(ref _reference, linearOffset), + shape + ); } - /// - /// Implicitly converts an array to a . - /// - public static implicit operator TensorSpan(T[]? array) => new TensorSpan(array); + /// + public override string ToString() => $"System.Numerics.Tensors.TensorSpan<{typeof(T).Name}>[{_shape}]"; - /// - /// Implicitly converts a to a - /// - public static implicit operator ReadOnlyTensorSpan(TensorSpan span) => - new ReadOnlyTensorSpan(ref span._reference, span._shape.Lengths, span._shape.Strides, span._shape._memoryLength); - - /// - /// For , returns a new instance of string that represents the characters pointed to by the span. - /// Otherwise, returns a with the name of the type and the number of elements. - /// - public override string ToString() => $"System.Numerics.Tensors.TensorSpan<{typeof(T).Name}>[{_shape.FlattenedLength}]"; - - /// - /// Returns a reference to specified element of the TensorSpan. - /// - /// The indexes for the slice. - /// - /// - /// Any index is less than 0 or greater than or equal to FlattenedLength. - /// - public TensorSpan Slice(params scoped ReadOnlySpan indexes) - { - NRange[] ranges = new NRange[indexes.Length]; - for (int i = 0; i < indexes.Length; i++) - { - ranges[i] = new NRange(checked((int)indexes[i].GetOffset(Lengths[i])), Lengths[i]); - } - return Slice(ranges); - } + /// + public bool TryCopyTo(scoped in TensorSpan destination) => AsReadOnlyTensorSpan().TryCopyTo(destination); - /// - /// Slices a span according to the provided lengths of the dimensions. - /// - /// The dimension lengths. - /// A based on the provided . - internal TensorSpan Slice(params scoped ReadOnlySpan lengths) - { - NRange[] ranges = new NRange[lengths.Length]; - for (int i = 0; i < lengths.Length; i++) - { - ranges[i] = new NRange(0, lengths[i]); - } - return Slice(ranges); - } + /// + public bool TryFlattenTo(scoped Span destination) => AsReadOnlyTensorSpan().TryFlattenTo(destination); - /// - /// Forms a slice out of the given span. - /// - /// The ranges for the slice. - /// A based on the provided . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TensorSpan Slice(params scoped ReadOnlySpan ranges) + /// Enumerates the elements of a tensor span. + public ref struct Enumerator : IEnumerator { - if (ranges.Length != Lengths.Length) - ThrowHelper.ThrowIndexOutOfRangeException(); - - TensorSpan toReturn; - scoped Span lengths; - scoped Span offsets; - nint[]? lengthsArray; - nint[]? offsetsArray; - if (Rank > TensorShape.MaxInlineRank) - { - lengthsArray = ArrayPool.Shared.Rent(Rank); - lengths = lengthsArray.AsSpan(0, Rank); + private readonly TensorSpan _span; + private nint[] _indexes; + private nint _linearOffset; + private nint _itemsEnumerated; - offsetsArray = ArrayPool.Shared.Rent(Rank); - offsets = offsetsArray.AsSpan(0, Rank); - } - else + internal Enumerator(TensorSpan span) { - lengths = stackalloc nint[Rank]; - offsets = stackalloc nint[Rank]; + _span = span; + _indexes = new nint[span.Rank]; - lengthsArray = null; - offsetsArray = null; - } - lengths.Clear(); - offsets.Clear(); + _indexes[^1] = -1; - for (int i = 0; i < ranges.Length; i++) - { - (offsets[i], lengths[i]) = ranges[i].GetOffsetAndLength(Lengths[i]); + _linearOffset = 0 - (!span.IsEmpty ? span.Strides[^1] : 0); + _itemsEnumerated = 0; } - // When we have an empty Tensor and someone wants to slice all of it, we should return an empty Tensor. - // FlattenedLength is computed everytime so using a local to cache the value. - nint flattenedLength = FlattenedLength; - nint index = 0; + /// Gets the element at the current position of the enumerator. + public readonly ref T Current => ref Unsafe.Add(ref _span._reference, _linearOffset); - if (flattenedLength != 0) + /// Advances the enumerator to the next element of the span. + public bool MoveNext() { - for (int i = 0; i < offsets.Length; i++) + if (_itemsEnumerated == _span._shape.FlattenedLength) { - index += Strides[i] * (offsets[i]); + return false; } - } - - if ((index >= _shape._memoryLength || index < 0) && flattenedLength != 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - toReturn = new TensorSpan(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index); + _linearOffset = _span._shape.AdjustToNextIndex(_span._shape, _linearOffset, _indexes); - if (offsetsArray != null) - ArrayPool.Shared.Return(offsetsArray); - if (lengthsArray != null) - ArrayPool.Shared.Return(lengthsArray); - - return toReturn; - } + _itemsEnumerated++; + return true; + } - /// - /// Flattens the contents of this span into the provided . - /// - /// The span to copy items into. - public bool TryFlattenTo(scoped Span destination) - { - bool retVal = false; - if (destination.Length <= _shape.FlattenedLength) + /// Sets the enumerator to its initial position, which is before the first element in the tensor span. + public void Reset() { - FlattenTo(destination); - retVal = true; + Array.Clear(_indexes); + _indexes[^1] = -1; + + _linearOffset = 0 - (!_span.IsEmpty ? _span.Strides[^1] : 0); + _itemsEnumerated = 0; } - return retVal; - } - /// - /// Flattens the contents of this span into the provided . - /// - /// The span to copy items into. - public void FlattenTo(scoped Span destination) - { - if (destination.Length < _shape.FlattenedLength) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); + // + // IDisposable + // - if (_shape.FlattenedLength == 0) - return; + void IDisposable.Dispose() { } - scoped Span curIndexes; - nint[]? curIndexesArray; - if (Rank > TensorShape.MaxInlineRank) - { - curIndexesArray = ArrayPool.Shared.Rent(Rank); - curIndexes = curIndexesArray.AsSpan(0, Rank); - } - else - { - curIndexesArray = null; - curIndexes = stackalloc nint[Rank]; - } - curIndexes.Clear(); + // + // IEnumerator + // - nint copiedValues = 0; - while (copiedValues < _shape.FlattenedLength) - { - TensorSpanHelpers.Memmove(destination.Slice(checked((int)copiedValues)), ref Unsafe.Add(ref _reference, TensorSpanHelpers.ComputeLinearIndex(curIndexes, Strides, Lengths)), Lengths[Rank - 1]); - TensorSpanHelpers.AdjustIndexes(Rank - 2, 1, curIndexes, _shape.Lengths); - copiedValues += Lengths[Rank - 1]; - } + readonly object? IEnumerator.Current => Current; + + // + // IEnumerator + // - if (curIndexesArray != null) - ArrayPool.Shared.Return(curIndexesArray); + readonly T IEnumerator.Current => Current; } } } diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.T.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.T.cs deleted file mode 100644 index f16e0c033d9a0c..00000000000000 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.T.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Numerics.Tensors -{ - internal static partial class TensorSpanHelpers // .T - { - public static unsafe void Memmove(Span destination, ReadOnlySpan source, nint length, nint dstOffset = 0) - { - source.Slice(0, checked((int)length)).CopyTo(destination.Slice(checked((int)dstOffset))); - } - - public static unsafe void Memmove(ref T[] destination, ref T source, nint length) - { - MemoryMarshal.CreateSpan(ref source, checked((int)length)).CopyTo(destination); - } - - public static unsafe void Memmove(ref T destination, ref T source, nint length) - { - MemoryMarshal.CreateSpan(ref source, checked((int)length)).CopyTo(MemoryMarshal.CreateSpan(ref destination, checked((int)length))); - } - - public static unsafe void Memmove(Span destination, ref T source, nint length) - { - MemoryMarshal.CreateSpan(ref source, checked((int)length)).CopyTo(destination); - } - - public static void Clear(ref T dest, nuint len) - { - while (len > 0) - { - nuint toClear = Math.Min(len, int.MaxValue); - MemoryMarshal.CreateSpan(ref dest, (int)toClear).Clear(); - dest = ref Unsafe.Add(ref dest, toClear); - len -= toClear; - } - } - - public static unsafe void Fill(ref T dest, nuint numElements, T value) - { - while (numElements > 0) - { - nuint toFill = Math.Min(numElements, int.MaxValue); - MemoryMarshal.CreateSpan(ref dest, (int)toFill).Fill(value); - dest = ref Unsafe.Add(ref dest, toFill); - numElements -= toFill; - } - } - } -} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs deleted file mode 100644 index 0d5623eeed8303..00000000000000 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs +++ /dev/null @@ -1,306 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace System.Numerics.Tensors -{ - - [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] - internal static partial class TensorSpanHelpers - { - internal static bool AreShapesTheSame(ReadOnlyTensorSpan tensor1, ReadOnlyTensorSpan tensor2) - where T : IEquatable, IEqualityOperators => tensor1._shape.Lengths.SequenceEqual(tensor2._shape.Lengths); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint CalculateTotalLength(ReadOnlySpan lengths) - { - if (lengths.IsEmpty) - return 0; - nint totalLength = 1; - for (int i = 0; i < lengths.Length; i++) - { - if (lengths[i] < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - totalLength *= lengths[i]; - } - - if (totalLength < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return totalLength; - } - - /// - /// Gets the set of strides that can be used to calculate the offset of n-dimensions in a 1-dimensional layout - /// - /// - public static nint[] CalculateStrides(ReadOnlySpan lengths) - { - nint[] strides = new nint[lengths.Length]; - - if (lengths.Length == 1 && lengths[0] == 0 || lengths.Length == 0) - { - strides[0] = 0; - return strides; - } - - nint stride = 1; - - for (int i = strides.Length - 1; i >= 0; i--) - { - strides[i] = stride; - stride *= lengths[i]; - } - - return strides; - } - - /// - /// Gets the set of strides that can be used to calculate the offset of n-dimensions in a 1-dimensional layout - /// - /// - public static nint[] CalculateStrides(ReadOnlySpan lengths, nint linearLength) - { - nint[] strides = new nint[lengths.Length]; - - if (linearLength == 0) - return strides; - - nint stride = 1; - - for (int i = strides.Length - 1; i >= 0; i--) - { - strides[i] = stride; - stride *= lengths[i]; - } - - return strides; - } - - /// - /// Calculates the 1-d index for n-d indexes in layout specified by strides. - /// - /// - /// - /// - /// - public static nint ComputeLinearIndex(ReadOnlySpan indexes, ReadOnlySpan strides, ReadOnlySpan lengths) - { - Debug.Assert(strides.Length == indexes.Length); - - nint index = 0; - for (int i = 0; i < indexes.Length; i++) - { - if (indexes[i] >= lengths[i] || indexes[i] < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - index += strides[i] * indexes[i]; - } - - return index; - } - - public static nint ComputeMaxLinearIndex(ReadOnlySpan strides, ReadOnlySpan lengths) - { - Debug.Assert(strides.Length == lengths.Length); - - nint index = 0; - for (int i = 0; i < lengths.Length; i++) - { - index += strides[i] * (lengths[i] - 1); - } - - return index; - } - - /// - /// Calculates the 1-d index for n-d indexes in layout specified by strides. - /// - /// - /// - /// - public static nint ComputeStartOffsetSystemArray(Array array, ReadOnlySpan indexes) - { - Debug.Assert(array.Rank == indexes.Length || indexes.Length == 0); - - if (indexes.Length == 0) - return 0; - - nint index = indexes[indexes.Length - 1]; - for (int i = indexes.Length - 2; i >= 0; i--) - { - if ((indexes[i] != 0 && indexes[i] >= array.GetLength(i)) || indexes[i] < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - index += array.GetLength(i) * indexes[i]; - } - - return index; - } - - /// - /// Calculates the 1-d index for n-d indexes in layout specified by strides. - /// - /// - /// - /// - public static nint ComputeStartOffsetSystemArray(Array array, ReadOnlySpan indexes) - { - Debug.Assert(array.Rank == indexes.Length || indexes.Length == 0); - - if (indexes.Length == 0) - return 0; - - nint index = indexes[indexes.Length - 1].GetOffset(array.GetLength(indexes.Length - 1)); - for (int i = indexes.Length - 2; i >= 0; i--) - { - nint offset = indexes[i].GetOffset(array.GetLength(i)); - if ((offset != 0 && offset >= array.GetLength(i)) || offset < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - index += array.GetLength(i) * offset; - } - - return index; - } - - /// - /// Calculates the 1-d index for n-d indexes in layout specified by strides. - /// - /// - /// - /// - /// - public static nint ComputeLinearIndex(ReadOnlySpan indexes, ReadOnlySpan strides, ReadOnlySpan lengths) - { - Debug.Assert(strides.Length == indexes.Length); - - nint index = 0; - for (int i = 0; i < indexes.Length; i++) - { - nint offset = indexes[i].GetOffset(lengths[i]); - if (offset >= lengths[i] || offset < 0) - ThrowHelper.ThrowIndexOutOfRangeException(); - index += strides[i] * offset; - } - - return index; - } - - public static void ValidateStrides(ReadOnlySpan strides, ReadOnlySpan lengths) - { - if (strides.Length != lengths.Length) - ThrowHelper.ThrowArgument_InvalidStridesAndLengths(); - - if (strides.Length == 0) - return; - - if (strides[lengths.Length - 1] < 0) - ThrowHelper.ThrowArgument_StrideLessThan0(); - - for (int i = lengths.Length - 1; i > 0; i--) - { - if (strides[i - 1] == 0) - continue; - else if (strides[i - 1] < 0) - ThrowHelper.ThrowArgument_StrideLessThan0(); - if (strides[i - 1] < strides[i] * lengths[i]) - ThrowHelper.ThrowArgument_StrideLessThan0(); - } - } - - /// - /// Takes the span holding the current index and increments it by the addend. If the length of the current spot is greater than the - /// length of that dimension then it rolls that over to the next dimension. - /// - /// The current index from the indexes we are on. - /// How much we are adding to the - /// The current indexes - /// The length of the TensorSpan we are iterating over. - public static void AdjustIndexes(int curIndex, nint addend, Span curIndexes, scoped ReadOnlySpan length) - { - if (addend <= 0 || curIndex < 0) - return; - curIndexes[curIndex] += addend; - - (nint Quotient, nint Remainder) result = Math.DivRem(curIndexes[curIndex], length[curIndex]); - - AdjustIndexes(curIndex - 1, result.Quotient, curIndexes, length); - curIndexes[curIndex] = result.Remainder; - } - - /// - /// Takes the span holding the current index and increments it by the addend. If the length of the current spot is greater than the - /// length of that dimension then it rolls that over to the next dimension. - /// - /// The current index from the indexes we are on. - /// How much we are adding to the - /// The current indexes - /// The length of the TensorSpan we are iterating over. - public static void AdjustIndexes(int curIndex, nint addend, ref nint[] curIndexes, ReadOnlySpan shape) - { - if (addend <= 0 || curIndex < 0) - return; - curIndexes[curIndex] += addend; - - (nint Quotient, nint Remainder) result = Math.DivRem(curIndexes[curIndex], shape[curIndex]); - - AdjustIndexes(curIndex - 1, result.Quotient, ref curIndexes, shape); - curIndexes[curIndex] = result.Remainder; - } - - /// - /// Takes the span holding the current index and decrements it by the addend. If the length of the current spot is greater than the - /// length of that dimension then it rolls that over to the next dimension. - /// - /// The current index from the indexes we are on. - /// How much we are subtracting from the - /// The current indexes - /// The length of the TensorSpan we are iterating over. - public static void AdjustIndexesDown(int curIndex, nint addend, Span curIndexes, ReadOnlySpan shape) - { - if (addend <= 0 || curIndex < 0) - return; - - curIndexes[curIndex] -= addend; - if (curIndexes[curIndex] < 0) - { - curIndexes[curIndex] = shape[curIndex] - 1; - AdjustIndexes(curIndex - 1, 1, curIndexes, shape); - } - } - - /// Fills with the corresponding lengths of the ranks in . - public static ReadOnlySpan FillLengths(Span lengths, Array array) - { - Debug.Assert(array is not null); - Debug.Assert(array.Rank == lengths.Length); - - for (int i = 0; i < lengths.Length; i++) - { - lengths[i] = array.GetLength(i); - } - - return lengths; - } - - /// Fills with values [i] = i. - public static void FillRange(Span span) - { - for (int i = 0; i < span.Length; i++) - { - span[i] = i; - } - } - - /// Fills with values [i] = i. - public static void FillRange(Span span) - { - for (int i = 0; i < span.Length; i++) - { - span[i] = i; - } - } - } -} diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs new file mode 100644 index 00000000000000..a2417ae57f1afe --- /dev/null +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor_1.cs @@ -0,0 +1,451 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Numerics.Tensors +{ + /// + /// Represents a tensor. + /// + [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public sealed class Tensor : ITensor, T> + { + /// Gets an empty tensor. + public static Tensor Empty { get; } = new(); + + internal readonly TensorShape _shape; + internal readonly T[] _values; + + internal readonly int _start; + internal readonly bool _isPinned; + + internal Tensor(scoped ReadOnlySpan lengths, bool pinned) + { + _shape = TensorShape.Create(lengths); + _values = GC.AllocateArray(checked((int)(_shape.LinearLength)), pinned); + + _start = 0; + _isPinned = pinned; + } + + internal Tensor(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned) + { + _shape = TensorShape.Create(lengths, strides); + _values = GC.AllocateArray(checked((int)(_shape.LinearLength)), pinned); + + _start = 0; + _isPinned = pinned; + } + + internal Tensor(T[]? array) + { + _shape = TensorShape.Create(array); + _values = (array is not null) ? array : []; + + _start = 0; + _isPinned = false; + } + + internal Tensor(T[]? array, scoped ReadOnlySpan lengths) + { + _shape = TensorShape.Create(array, lengths); + _values = (array is not null) ? array : []; + + _start = 0; + _isPinned = false; + } + + internal Tensor(T[]? array, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(array, lengths, strides); + _values = (array is not null) ? array : []; + + _start = 0; + _isPinned = false; + } + + internal Tensor(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides) + { + _shape = TensorShape.Create(array, start, lengths, strides); + _values = (array is not null) ? array : []; + + _start = start; + _isPinned = false; + } + + internal Tensor(T[]? array, int start, scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, scoped ReadOnlySpan linearRankOrder) + { + _shape = TensorShape.Create(array, start, lengths, strides, linearRankOrder); + _values = (array is not null) ? array : []; + + _start = start; + _isPinned = false; + } + + internal Tensor(T[] array, in TensorShape shape, bool isPinned) + { + ThrowHelper.ThrowIfArrayTypeMismatch(array); + + _shape = shape; + _values = array; + + _start = 0; + _isPinned = isPinned; + } + + internal Tensor(T[] array, int start, in TensorShape shape, bool isPinned) + { + ThrowHelper.ThrowIfArrayTypeMismatch(array); + + _shape = shape; + _values = array; + + _start = start; + _isPinned = isPinned; + } + + private Tensor() + { + _shape = default; + _values = []; + + _start = 0; + _isPinned = false; + } + + /// + public ref T this[params scoped ReadOnlySpan indexes] + { + get => ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_values), _start + _shape.GetLinearOffset(indexes)); + } + + /// + public ref T this[params scoped ReadOnlySpan indexes] + { + get => ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_values), _start + _shape.GetLinearOffset(indexes)); + } + + /// + public Tensor this[params ReadOnlySpan ranges] + { + get => Slice(ranges); + set => value.CopyTo(Slice(ranges)); + } + + /// + public nint FlattenedLength => _shape.FlattenedLength; + + internal bool IsContiguousAndDense => _shape.IsContiguousAndDense; + + /// + public bool IsEmpty => _shape.IsEmpty; + + /// + public bool IsPinned => _isPinned; + + /// + public ReadOnlySpan Lengths => _shape.Lengths; + + /// + public int Rank => _shape.Rank; + + /// + public ReadOnlySpan Strides => _shape.Strides; + + /// Defines an implicit conversion of an array to a tensor. + /// The array to convert to a tensor. + /// The tensor span that corresponds to . + public static implicit operator Tensor(T[] array) => Tensor.Create(array); + + /// Defines an implicit conversion of a tensor to a tensor span. + /// The tensor to convert to a tensor span. + /// The tensor that corresponds to . + public static implicit operator TensorSpan(Tensor tensor) => tensor.AsTensorSpan(); + + /// + public static implicit operator ReadOnlyTensorSpan(Tensor tensor) => tensor.AsReadOnlyTensorSpan(); + + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan() => new ReadOnlyTensorSpan(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_values), _start), in _shape); + + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndexes) => AsReadOnlyTensorSpan().Slice(startIndexes); + + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan startIndexes) => AsReadOnlyTensorSpan().Slice(startIndexes); + + /// + public ReadOnlyTensorSpan AsReadOnlyTensorSpan(params scoped ReadOnlySpan ranges) => AsReadOnlyTensorSpan().Slice(ranges); + + /// + public TensorSpan AsTensorSpan() => new TensorSpan(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_values), _start), in _shape); + + /// + public TensorSpan AsTensorSpan(params scoped ReadOnlySpan startIndexes) => AsTensorSpan().Slice(startIndexes); + + /// + public TensorSpan AsTensorSpan(params scoped ReadOnlySpan startIndexes) => AsTensorSpan().Slice(startIndexes); + + /// + public TensorSpan AsTensorSpan(params scoped ReadOnlySpan ranges) => AsTensorSpan().Slice(ranges); + + /// + public unsafe void Clear() => AsTensorSpan().Clear(); + + /// + public void CopyTo(scoped in TensorSpan destination) + { + if (!TryCopyTo(destination)) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + } + + /// + public void Fill(T value) => AsTensorSpan().Fill(value); + + /// + public void FlattenTo(scoped Span destination) + { + if (!TryFlattenTo(destination)) + { + ThrowHelper.ThrowArgument_DestinationTooShort(); + } + } + + /// Gets an enumerator for the readonly tensor. + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public ref T GetPinnableReference() + { + // Ensure that the native code has just one forward branch that is predicted-not-taken. + ref T ret = ref Unsafe.NullRef(); + if (_shape.FlattenedLength != 0) ret = ref MemoryMarshal.GetArrayDataReference(_values); + return ref ret; + } + + /// + public unsafe MemoryHandle GetPinnedHandle() + { + GCHandle handle = GCHandle.Alloc(_values, GCHandleType.Pinned); + return new MemoryHandle(Unsafe.AsPointer(ref GetPinnableReference()), handle); + } + + /// + public Tensor Slice(params ReadOnlySpan startIndexes) + { + TensorShape shape = _shape.Slice(startIndexes, out nint linearOffset); + + // The source tensor can have no more than int.MaxValue elements so linearOffset will always be in range of int. + Debug.Assert((int)(linearOffset) == linearOffset); + + return new Tensor( + _values, + (int)(_start + linearOffset), + in shape, + _isPinned + ); + } + + /// + public Tensor Slice(params ReadOnlySpan startIndexes) + { + TensorShape shape = _shape.Slice(startIndexes, out nint linearOffset); + + // The source tensor can have no more than int.MaxValue elements so linearOffset will always be in range of int. + Debug.Assert((int)(linearOffset) == linearOffset); + + return new Tensor( + _values, + (int)(_start + linearOffset), + in shape, + _isPinned + ); + } + + /// + public Tensor Slice(params ReadOnlySpan ranges) + { + TensorShape shape = _shape.Slice(ranges, out nint linearOffset); + + // The source tensor can have no more than int.MaxValue elements so linearOffset will always be in range of int. + Debug.Assert((int)(linearOffset) == linearOffset); + + return new Tensor( + _values, + (int)(_start + linearOffset), + in shape, + _isPinned + ); + } + + /// + public bool TryCopyTo(scoped in TensorSpan destination) => AsReadOnlyTensorSpan().TryCopyTo(destination); + + /// + public bool TryFlattenTo(scoped Span destination) => AsReadOnlyTensorSpan().TryFlattenTo(destination); + + /// + /// Creates a representation of the ."/> + /// + /// Maximum Length of each dimension + /// A representation of the + public string ToString(params ReadOnlySpan maximumLengths) + { + var sb = new StringBuilder($"System.Numerics.Tensors.Tensor<{typeof(T).Name}>[{_shape}]"); + + sb.AppendLine("{"); + Tensor.ToString(AsReadOnlyTensorSpan(), maximumLengths, sb); + sb.AppendLine("}"); + + return sb.ToString(); + } + + // + // IEnumerable + // + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + // + // IEnumerable + // + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + // + // IReadOnlyTensor + // + + object? IReadOnlyTensor.this[params scoped ReadOnlySpan indexes] => this[indexes]; + + object? IReadOnlyTensor.this[params scoped ReadOnlySpan indexes] => this[indexes]; + + // + // IReadOnlyTensor + // + + ref readonly T IReadOnlyTensor, T>.this[params ReadOnlySpan indexes] => ref this[indexes]; + + ref readonly T IReadOnlyTensor, T>.this[params ReadOnlySpan indexes] => ref this[indexes]; + + [EditorBrowsable(EditorBrowsableState.Never)] + ref readonly T IReadOnlyTensor, T>.GetPinnableReference() => ref GetPinnableReference(); + + // + // ITensor + // + + bool ITensor.IsReadOnly => false; + + object? ITensor.this[params scoped ReadOnlySpan indexes] + { + get => this[indexes]; + + set + { + this[indexes] = (T)value!; + } + } + + object? ITensor.this[params scoped ReadOnlySpan indexes] + { + get => this[indexes]; + + set + { + this[indexes] = (T)value!; + } + } + + void ITensor.Fill(object value) => Fill(value is T t ? t : throw new ArgumentException($"Cannot convert {value} to {typeof(T)}")); + + // + // ITensor + // + + static Tensor ITensor, T>.Create(scoped ReadOnlySpan lengths, bool pinned) => Tensor.Create(lengths, pinned); + + static Tensor ITensor, T>.Create(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned) => Tensor.Create(lengths, strides, pinned); + + static Tensor ITensor, T>.CreateUninitialized(scoped ReadOnlySpan lengths, bool pinned) => Tensor.Create(lengths, pinned); + + static Tensor ITensor, T>.CreateUninitialized(scoped ReadOnlySpan lengths, scoped ReadOnlySpan strides, bool pinned) => Tensor.Create(lengths, strides, pinned); + + /// Enumerates the elements of a tensor. + public struct Enumerator : IEnumerator + { + private readonly Tensor _tensor; + private nint[] _indexes; + private nint _linearOffset; + private nint _itemsEnumerated; + + internal Enumerator(Tensor tensor) + { + _tensor = tensor; + _indexes = new nint[tensor.Rank]; + + _indexes[^1] = -1; + + _linearOffset = tensor._start - (!tensor.IsEmpty ? tensor.Strides[^1] : 0); + _itemsEnumerated = 0; + } + + /// + public readonly ref T Current => ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_tensor._values), _linearOffset); + + /// + public bool MoveNext() + { + if (_itemsEnumerated == _tensor._shape.FlattenedLength) + { + return false; + } + + _linearOffset = _tensor._shape.AdjustToNextIndex(_tensor._shape, _linearOffset, _indexes); + + _itemsEnumerated++; + return true; + } + + /// + public void Reset() + { + Array.Clear(_indexes); + _indexes[^1] = -1; + + _linearOffset = _tensor._start - (!_tensor.IsEmpty ? _tensor.Strides[^1] : 0); + _itemsEnumerated = 0; + } + + // + // IDisposable + // + + readonly void IDisposable.Dispose() { } + + // + // IEnumerator + // + + readonly object? IEnumerator.Current => Current; + + // + // IEnumerator + // + + readonly T IEnumerator.Current => Current; + } + } +} diff --git a/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs b/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs index d2396172fb606e..81338921a5810d 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/ThrowHelper.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace System { @@ -28,6 +29,22 @@ public static void ThrowArgument_SpansMustBeNonEmpty() => public static void ThrowArgument_InputAndDestinationSpanMustNotOverlap() => throw new ArgumentException(SR.Argument_InputAndDestinationSpanMustNotOverlap, "destination"); + public static void ThrowIfArrayTypeMismatch(Array? array) + { + if ((array is not null) && (array.GetType().GetElementType() != typeof(T))) + { + ThrowArrayTypeMismatchException(); + } + } + + public static void ThrowIfArrayTypeMismatch(T[]? array) + { + if ((array is not null) && !typeof(T).IsValueType && (array.GetType() != typeof(T[]))) + { + ThrowArrayTypeMismatchException(); + } + } + [DoesNotReturn] public static void ThrowArgument_DestinationSpansMustNotOverlap() => throw new ArgumentException(SR.Argument_DestinationSpansMustNotOverlap); @@ -57,13 +74,19 @@ internal static void ThrowIndexOutOfRangeException() } [DoesNotReturn] - public static void ThrowArgumentException_DestinationTooShort() + public static void ThrowArgument_LengthsMustEqualArrayLength() { - throw GetArgumentException(SR.DestinationTooShort); + throw new ArgumentOutOfRangeException(); } [DoesNotReturn] - public static void ThrowArgument_LengthsMustEqualArrayLength() + public static void ThrowArgument_LengthIsNegative() + { + throw new ArgumentOutOfRangeException(); + } + + [DoesNotReturn] + public static void ThrowArgument_StartIndexOutOfBounds() { throw new ArgumentOutOfRangeException(); } @@ -102,9 +125,9 @@ public static void ThrowArgument_SetSliceInvalidShapes(string? paramNames) } [DoesNotReturn] - public static void ThrowArgument_LengthsNotBroadcastCompatible() + public static void ThrowArgument_LengthsNotCompatible() { - throw new ArgumentException(SR.ThrowArgument_LengthsNotBroadcastCompatible); + throw new ArgumentException(SR.ThrowArgument_LengthsNotCompatible); } [DoesNotReturn] @@ -204,13 +227,19 @@ public static void ThrowArgument_InPlaceInvalidShape() } [DoesNotReturn] - public static void ThrowArgument_InvalidStridesAndLengths() + public static void ThrowArgument_InvalidTensorShape() { throw new ArgumentException(SR.ThrowArgument_InvalidStridesAndLengths); } [DoesNotReturn] - public static void ThrowArgument_StrideLessThan0() + public static void ThrowArgument_LengthIsNonZeroForNullReference() + { + throw new ArgumentOutOfRangeException(SR.ThrowArgument_LengthIsNonZeroForNullReference); + } + + [DoesNotReturn] + public static void ThrowArgument_StrideIsNegative() { throw new ArgumentOutOfRangeException(SR.ThrowArgument_StrideLessThan0); } diff --git a/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs b/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs index d88ccfb9a7fbe6..1a3b5fd8825a68 100644 --- a/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs @@ -5,8 +5,6 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace System.Numerics.Tensors.Tests @@ -191,7 +189,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => + Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, (int[])[], [2, 2], [1, 1]); }); @@ -206,34 +204,34 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); - //Make sure it works with NIndex - spanInt = new ReadOnlyTensorSpan(a, (NIndex[])[1, 1], [2, 2], [0, 0]); - Assert.Equal(2, spanInt.Rank); - Assert.Equal(2, spanInt.Lengths[0]); - Assert.Equal(2, spanInt.Lengths[1]); - Assert.Equal(94, spanInt[0, 0]); - Assert.Equal(94, spanInt[0, 1]); - Assert.Equal(94, spanInt[1, 0]); - Assert.Equal(94, spanInt[1, 1]); - - //Make sure it works with NIndex - spanInt = new ReadOnlyTensorSpan(a, (NIndex[])[^1, ^1], [2, 2], [0, 0]); - Assert.Equal(2, spanInt.Rank); - Assert.Equal(2, spanInt.Lengths[0]); - Assert.Equal(2, spanInt.Lengths[1]); - Assert.Equal(94, spanInt[0, 0]); - Assert.Equal(94, spanInt[0, 1]); - Assert.Equal(94, spanInt[1, 0]); - Assert.Equal(94, spanInt[1, 1]); + // // TODO: Make sure it works with NIndex + // spanInt = new ReadOnlyTensorSpan(a, (NIndex[])[1, 1], [2, 2], [0, 0]); + // Assert.Equal(2, spanInt.Rank); + // Assert.Equal(2, spanInt.Lengths[0]); + // Assert.Equal(2, spanInt.Lengths[1]); + // Assert.Equal(94, spanInt[0, 0]); + // Assert.Equal(94, spanInt[0, 1]); + // Assert.Equal(94, spanInt[1, 0]); + // Assert.Equal(94, spanInt[1, 1]); + + // // TODO: Make sure it works with NIndex + // spanInt = new ReadOnlyTensorSpan(a, (NIndex[])[^1, ^1], [2, 2], [0, 0]); + // Assert.Equal(2, spanInt.Rank); + // Assert.Equal(2, spanInt.Lengths[0]); + // Assert.Equal(2, spanInt.Lengths[1]); + // Assert.Equal(94, spanInt[0, 0]); + // Assert.Equal(94, spanInt[0, 1]); + // Assert.Equal(94, spanInt[1, 0]); + // Assert.Equal(94, spanInt[1, 1]); } [Fact] public static void ReadOnlyTensorSpanArrayConstructorTests() { // Make sure exception is thrown if lengths and strides would let you go past the end of the array - Assert.Throws(() => new TensorSpan(new double[0], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); - Assert.Throws(() => new TensorSpan(new double[1], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); - Assert.Throws(() => new TensorSpan(new double[2], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 2 })); + Assert.Throws(() => new TensorSpan(new double[0], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); + Assert.Throws(() => new TensorSpan(new double[1], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); + Assert.Throws(() => new TensorSpan(new double[2], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 2 })); // Make sure basic T[] constructor works int[] a = { 91, 92, -93, 94 }; @@ -259,7 +257,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(1, spanInt.Rank); Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); - Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(1, spanInt.Strides[0]); // Make sure it still throws on index 0 Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(b); @@ -275,7 +273,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Strides[0]); // Make sure 2D array works - spanInt = new ReadOnlyTensorSpan(a, new Index(0), [2,2], default); + spanInt = new ReadOnlyTensorSpan(a, 0, [2,2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -285,24 +283,24 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[1,1]); // Make sure can use only some of the array - spanInt = new ReadOnlyTensorSpan(a, new Index(0), [1, 2], default); + spanInt = new ReadOnlyTensorSpan(a, 0, [1, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); Assert.Throws(() => { - var spanInt = new ReadOnlyTensorSpan(a, new Index(0), [1, 2], default); + var spanInt = new ReadOnlyTensorSpan(a, 0, [1, 2], default); var x = spanInt[1, 1]; }); Assert.Throws(() => { - var spanInt = new ReadOnlyTensorSpan(a, new Index(0), [1, 2], default); + var spanInt = new ReadOnlyTensorSpan(a, 0, [1, 2], default); var x = spanInt[1, 0]; }); // Make sure Index offset works correctly - spanInt = new ReadOnlyTensorSpan(a, new Index(1), [1, 2], default); + spanInt = new ReadOnlyTensorSpan(a, 1, [1, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -310,7 +308,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(-93, spanInt[0, 1]); // Make sure Index offset works correctly - spanInt = new ReadOnlyTensorSpan(a, new Index(2), [1, 2], default); + spanInt = new ReadOnlyTensorSpan(a, 2, [1, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -318,12 +316,12 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); // Make sure we catch that there aren't enough elements in the array for the lengths - Assert.Throws(() => { - var spanInt = new ReadOnlyTensorSpan(a, new Index(3), [1, 2], default); + Assert.Throws(() => { + var spanInt = new ReadOnlyTensorSpan(a, 3, [1, 2], default); }); // Make sure 2D array works with basic strides - spanInt = new ReadOnlyTensorSpan(a, new Index(0), [2, 2], [2, 1]); + spanInt = new ReadOnlyTensorSpan(a, 0, [2, 2], [2, 1]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -333,7 +331,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[1, 1]); // Make sure 2D array works with stride of 0 to loop over first 2 elements again - spanInt = new ReadOnlyTensorSpan(a, new Index(0), [2, 2], [0, 1]); + spanInt = new ReadOnlyTensorSpan(a, 0, [2, 2], [0, 1]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -343,7 +341,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[1, 1]); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again - spanInt = new ReadOnlyTensorSpan(a, new Index(2), [2, 2], [0, 1]); + spanInt = new ReadOnlyTensorSpan(a, 2, [2, 2], [0, 1]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -353,7 +351,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[1, 1]); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again - spanInt = new ReadOnlyTensorSpan(a, new Index(3), [2, 2], [0, 0]); + spanInt = new ReadOnlyTensorSpan(a, 3, [2, 2], [0, 0]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -364,18 +362,18 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() // Make sure strides can't be negative Assert.Throws(() => { - var spanInt = new ReadOnlyTensorSpan(a, new Index(3), [1, 2], [-1, 0]); + var spanInt = new ReadOnlyTensorSpan(a, 3, [1, 2], [-1, 0]); }); Assert.Throws(() => { - var spanInt = new ReadOnlyTensorSpan(a, new Index(3), [1, 2], [0, -1]); + var spanInt = new ReadOnlyTensorSpan(a, 3, [1, 2], [0, -1]); }); // Make sure lengths can't be negative Assert.Throws(() => { - var spanInt = new ReadOnlyTensorSpan(a, new Index(3), [-1, 2], []); + var spanInt = new ReadOnlyTensorSpan(a, 3, [-1, 2], []); }); Assert.Throws(() => { - var spanInt = new ReadOnlyTensorSpan(a, new Index(3), [1, -2], []); + var spanInt = new ReadOnlyTensorSpan(a, 3, [1, -2], []); }); // Make sure 2D array works with strides to hit element 0,0,2,2 @@ -389,7 +387,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => { + Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, 0, [2, 2], [1, 1]); }); } @@ -411,12 +409,12 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() // Should be a Tensor with 0 elements but Rank 1 with dimension 0 length 0 Span b = []; spanInt = new ReadOnlyTensorSpan(b); - Assert.Equal(1, spanInt.Rank); - Assert.Equal(0, spanInt.Lengths[0]); + Assert.Equal(0, spanInt.Rank); + Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.FlattenedLength); - Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides.Length); // Make sure it still throws on index 0 - Assert.Throws(() => { + Assert.Throws(() => { Span b = []; var spanInt = new ReadOnlyTensorSpan(b); var x = spanInt[0]; @@ -468,7 +466,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(94, spanInt[0, 1]); // Make sure we catch that there aren't enough elements in the array for the lengths - Assert.Throws(() => { + Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new ReadOnlyTensorSpan(a.Slice(3), [1, 2], default); }); @@ -544,7 +542,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => { + Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new ReadOnlyTensorSpan(a, [2, 2], [1, 1]); }); @@ -573,12 +571,12 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() fixed (int* p = b) { spanInt = new ReadOnlyTensorSpan(p, 0); - Assert.Equal(1, spanInt.Rank); - Assert.Equal(0, spanInt.Lengths[0]); + Assert.Equal(0, spanInt.Rank); + Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.FlattenedLength); - Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides.Length); // Make sure it still throws on index 0 - Assert.Throws(() => + Assert.Throws(() => { Span b = []; fixed (int* p = b) @@ -646,7 +644,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(94, spanInt[0, 1]); // Make sure we catch that there aren't enough elements in the array for the lengths - Assert.Throws(() => + Assert.Throws(() => { Span a = [91, 92, -93, 94]; fixed (int* p = a) @@ -752,7 +750,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => + Assert.Throws(() => { Span a = [91, 92, -93, 94]; fixed (int* p = a) @@ -768,7 +766,7 @@ public static void ReadOnlyTensorSpanLargeDimensionsTests() { int[] a = { 91, 92, -93, 94, 95, -96 }; int[] results = new int[6]; - ReadOnlyTensorSpan spanInt = a.AsTensorSpan(1, 1, 1, 1, 1, 6); + ReadOnlyTensorSpan spanInt = a.AsTensorSpan([1, 1, 1, 1, 1, 6]); Assert.Equal(6, spanInt.Rank); Assert.Equal(6, spanInt.Lengths.Length); @@ -779,11 +777,11 @@ public static void ReadOnlyTensorSpanLargeDimensionsTests() Assert.Equal(1, spanInt.Lengths[4]); Assert.Equal(6, spanInt.Lengths[5]); Assert.Equal(6, spanInt.Strides.Length); - Assert.Equal(6, spanInt.Strides[0]); - Assert.Equal(6, spanInt.Strides[1]); - Assert.Equal(6, spanInt.Strides[2]); - Assert.Equal(6, spanInt.Strides[3]); - Assert.Equal(6, spanInt.Strides[4]); + Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides[1]); + Assert.Equal(0, spanInt.Strides[2]); + Assert.Equal(0, spanInt.Strides[3]); + Assert.Equal(0, spanInt.Strides[4]); Assert.Equal(1, spanInt.Strides[5]); Assert.Equal(91, spanInt[0, 0, 0, 0, 0, 0]); Assert.Equal(92, spanInt[0, 0, 0, 0, 0, 1]); @@ -796,7 +794,7 @@ public static void ReadOnlyTensorSpanLargeDimensionsTests() a = [91, 92, -93, 94, 95, -96, -91, -92, 93, -94, -95, 96]; results = new int[12]; - spanInt = a.AsTensorSpan(1, 2, 2, 1, 1, 3); + spanInt = a.AsTensorSpan([1, 2, 2, 1, 1, 3]); Assert.Equal(6, spanInt.Lengths.Length); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -805,11 +803,11 @@ public static void ReadOnlyTensorSpanLargeDimensionsTests() Assert.Equal(1, spanInt.Lengths[4]); Assert.Equal(3, spanInt.Lengths[5]); Assert.Equal(6, spanInt.Strides.Length); - Assert.Equal(12, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides[0]); Assert.Equal(6, spanInt.Strides[1]); Assert.Equal(3, spanInt.Strides[2]); - Assert.Equal(3, spanInt.Strides[3]); - Assert.Equal(3, spanInt.Strides[4]); + Assert.Equal(0, spanInt.Strides[3]); + Assert.Equal(0, spanInt.Strides[4]); Assert.Equal(1, spanInt.Strides[5]); Assert.Equal(91, spanInt[0, 0, 0, 0, 0, 0]); Assert.Equal(92, spanInt[0, 0, 0, 0, 0, 1]); @@ -832,7 +830,7 @@ public static void IntArrayAsReadOnlyTensorSpan() { int[] a = { 91, 92, -93, 94 }; int[] results = new int[4]; - ReadOnlyTensorSpan spanInt = a.AsTensorSpan(4); + ReadOnlyTensorSpan spanInt = a.AsTensorSpan([4]); Assert.Equal(1, spanInt.Rank); Assert.Equal(1, spanInt.Lengths.Length); @@ -850,7 +848,7 @@ public static void IntArrayAsReadOnlyTensorSpan() a[1] = 92; a[2] = -93; a[3] = 94; - spanInt = a.AsTensorSpan(2, 2); + spanInt = a.AsTensorSpan([2, 2]); spanInt.FlattenTo(results); Assert.Equal(a, results); Assert.Equal(2, spanInt.Rank); @@ -871,8 +869,8 @@ public static void ReadOnlyTensorSpanCopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; - ReadOnlyTensorSpan leftSpan = leftData.AsTensorSpan(3, 3); - TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); + ReadOnlyTensorSpan leftSpan = leftData.AsTensorSpan([3, 3]); + TensorSpan rightSpan = rightData.AsTensorSpan([3, 3]); leftSpan.CopyTo(rightSpan); var leftEnum = leftSpan.GetEnumerator(); var rightEnum = rightSpan.GetEnumerator(); @@ -890,16 +888,16 @@ public static void ReadOnlyTensorSpanCopyTest() { leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; rightData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - TensorSpan leftSpan = leftData.AsTensorSpan(9); - TensorSpan tensor = rightData.AsTensorSpan(rightData.Length); + TensorSpan leftSpan = leftData.AsTensorSpan([9]); + TensorSpan tensor = rightData.AsTensorSpan([rightData.Length]); leftSpan.CopyTo(tensor); } ); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; - leftSpan = leftData.AsTensorSpan(3, 3, 3); - rightSpan = rightData.AsTensorSpan(3, 3, 3); + leftSpan = leftData.AsTensorSpan([3, 3, 3]); + rightSpan = rightData.AsTensorSpan([3, 3, 3]); leftSpan.CopyTo(rightSpan); while (leftEnum.MoveNext() && rightEnum.MoveNext()) @@ -909,7 +907,7 @@ public static void ReadOnlyTensorSpanCopyTest() Assert.Throws(() => { - var l = leftData.AsTensorSpan(3, 3, 3); + var l = leftData.AsTensorSpan([3, 3, 3]); var r = new TensorSpan(); l.CopyTo(r); }); @@ -920,8 +918,8 @@ public static void ReadOnlyTensorSpanTryCopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; - ReadOnlyTensorSpan leftSpan = leftData.AsTensorSpan(3, 3); - TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); + ReadOnlyTensorSpan leftSpan = leftData.AsTensorSpan([3, 3]); + TensorSpan rightSpan = rightData.AsTensorSpan([3, 3]); var success = leftSpan.TryCopyTo(rightSpan); Assert.True(success); var leftEnum = leftSpan.GetEnumerator(); @@ -937,15 +935,15 @@ public static void ReadOnlyTensorSpanTryCopyTest() leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; rightData = new int[15]; - leftSpan = leftData.AsTensorSpan(9); - rightSpan = rightData.AsTensorSpan(15); + leftSpan = leftData.AsTensorSpan([9]); + rightSpan = rightData.AsTensorSpan([15]); success = leftSpan.TryCopyTo(rightSpan); Assert.False(success); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; - leftSpan = leftData.AsTensorSpan(3, 3, 3); - rightSpan = rightData.AsTensorSpan(3, 3, 3); + leftSpan = leftData.AsTensorSpan([3, 3, 3]); + rightSpan = rightData.AsTensorSpan([3, 3, 3]); success = leftSpan.TryCopyTo(rightSpan); Assert.True(success); @@ -954,7 +952,7 @@ public static void ReadOnlyTensorSpanTryCopyTest() Assert.Equal(leftEnum.Current, rightEnum.Current); } - var l = leftData.AsTensorSpan(3, 3, 3); + var l = leftData.AsTensorSpan([3, 3, 3]); var r = new TensorSpan(); success = l.TryCopyTo(r); Assert.False(success); @@ -1014,11 +1012,11 @@ public static void ReadOnlyTensorSpanSliceTest() int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; - ReadOnlyTensorSpan spanInt = a.AsTensorSpan(3, 3); + ReadOnlyTensorSpan spanInt = a.AsTensorSpan([3, 3]); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1)); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(1..2)); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1, 5..6)); + Assert.Throws(() => a.AsTensorSpan([2, 3]).Slice(0..1)); + Assert.Throws(() => a.AsTensorSpan([2, 3]).Slice(1..2)); + Assert.Throws(() => a.AsTensorSpan([2, 3]).Slice(0..1, 5..6)); var sp = spanInt.Slice(1..3, 1..3); Assert.Equal(5, sp[0, 0]); @@ -1058,7 +1056,7 @@ public static void ReadOnlyTensorSpanSliceTest() sp = spanInt.Slice(0..1, 0..1); Assert.Equal(1, sp[0, 0]); - Assert.Throws(() => a.AsTensorSpan(3, 3).Slice(0..1, 0..1)[0, 1]); + Assert.Throws(() => a.AsTensorSpan([3, 3]).Slice(0..1, 0..1)[0, 1]); slice = [1]; results = new int[1]; sp.FlattenTo(results); @@ -1087,7 +1085,7 @@ public static void ReadOnlyTensorSpanSliceTest() } int[] numbers = [.. Enumerable.Range(0, 27)]; - spanInt = numbers.AsTensorSpan(3, 3, 3); + spanInt = numbers.AsTensorSpan([3, 3, 3]); sp = spanInt.Slice(1..2, 1..2, 1..2); Assert.Equal(13, sp[0, 0, 0]); slice = [13]; @@ -1122,7 +1120,7 @@ public static void ReadOnlyTensorSpanSliceTest() } numbers = [.. Enumerable.Range(0, 16)]; - spanInt = numbers.AsTensorSpan(2, 2, 2, 2); + spanInt = numbers.AsTensorSpan([2, 2, 2, 2]); sp = spanInt.Slice(1..2, 0..2, 1..2, 0..2); Assert.Equal(10, sp[0, 0, 0, 0]); Assert.Equal(11, sp[0, 0, 0, 1]); @@ -1144,7 +1142,7 @@ public static void ReadOnlyTensorSpanSliceTest() public static void LongArrayAsReadOnlyTensorSpan() { long[] b = { 91, -92, 93, 94, -95 }; - ReadOnlyTensorSpan spanLong = b.AsTensorSpan(5); + ReadOnlyTensorSpan spanLong = b.AsTensorSpan([5]); Assert.Equal(91, spanLong[0]); Assert.Equal(-92, spanLong[1]); Assert.Equal(93, spanLong[2]); diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs index 0d0fc167b6bfcd..b4616cd4f0d77d 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs @@ -4,7 +4,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; -using System.Reflection; +using System.Linq.Expressions; using System.Runtime.InteropServices; using Xunit; @@ -35,61 +35,61 @@ private static nint CalculateTotalLength(ReadOnlySpan lengths) return totalLength; } - public delegate void TensorPrimitivesSpanInSpanOut(ReadOnlySpan input, Span output); + public delegate TOut TensorPrimitivesSpanInSpanOut(TIn input); public delegate ref readonly TensorSpan TensorSpanInSpanOut(scoped in ReadOnlyTensorSpan input, in TensorSpan destination); public static IEnumerable SpanInSpanOutData() { - yield return Create(TensorPrimitives.Abs, Tensor.Abs); - yield return Create(TensorPrimitives.Acos, Tensor.Acos); - yield return Create(TensorPrimitives.Acosh, Tensor.Acosh); - yield return Create(TensorPrimitives.AcosPi, Tensor.AcosPi); - yield return Create(TensorPrimitives.Asin, Tensor.Asin); - yield return Create(TensorPrimitives.Asinh, Tensor.Asinh); - yield return Create(TensorPrimitives.AsinPi, Tensor.AsinPi); - yield return Create(TensorPrimitives.Atan, Tensor.Atan); - yield return Create(TensorPrimitives.Atanh, Tensor.Atanh); - yield return Create(TensorPrimitives.AtanPi, Tensor.AtanPi); - yield return Create(TensorPrimitives.Cbrt, Tensor.Cbrt); - yield return Create(TensorPrimitives.Ceiling, Tensor.Ceiling); - yield return Create(TensorPrimitives.Cos, Tensor.Cos); - yield return Create(TensorPrimitives.Cosh, Tensor.Cosh); - yield return Create(TensorPrimitives.CosPi, Tensor.CosPi); - yield return Create(TensorPrimitives.DegreesToRadians, Tensor.DegreesToRadians); - yield return Create(TensorPrimitives.Exp, Tensor.Exp); - yield return Create(TensorPrimitives.Exp10, Tensor.Exp10); - yield return Create(TensorPrimitives.Exp10M1, Tensor.Exp10M1); - yield return Create(TensorPrimitives.Exp2, Tensor.Exp2); - yield return Create(TensorPrimitives.Exp2M1, Tensor.Exp2M1); - yield return Create(TensorPrimitives.ExpM1, Tensor.ExpM1); - yield return Create(TensorPrimitives.Floor, Tensor.Floor); - yield return Create(TensorPrimitives.LeadingZeroCount, Tensor.LeadingZeroCount); - yield return Create(TensorPrimitives.Log, Tensor.Log); - yield return Create(TensorPrimitives.Log10, Tensor.Log10); - yield return Create(TensorPrimitives.Log10P1, Tensor.Log10P1); - yield return Create(TensorPrimitives.Log2, Tensor.Log2); - yield return Create(TensorPrimitives.Log2P1, Tensor.Log2P1); - yield return Create(TensorPrimitives.LogP1, Tensor.LogP1); - yield return Create(TensorPrimitives.Negate, Tensor.Negate); - yield return Create(TensorPrimitives.OnesComplement, Tensor.OnesComplement); - yield return Create(TensorPrimitives.PopCount, Tensor.PopCount); - yield return Create(TensorPrimitives.RadiansToDegrees, Tensor.RadiansToDegrees); - yield return Create(TensorPrimitives.Reciprocal, Tensor.Reciprocal); - yield return Create(TensorPrimitives.Round, Tensor.Round); - yield return Create(TensorPrimitives.Sigmoid, Tensor.Sigmoid); - yield return Create(TensorPrimitives.Sin, Tensor.Sin); - yield return Create(TensorPrimitives.Sinh, Tensor.Sinh); - yield return Create(TensorPrimitives.SinPi, Tensor.SinPi); - yield return Create(TensorPrimitives.SoftMax, Tensor.SoftMax); - yield return Create(TensorPrimitives.Sqrt, Tensor.Sqrt); - yield return Create(TensorPrimitives.Tan, Tensor.Tan); - yield return Create(TensorPrimitives.Tanh, Tensor.Tanh); - yield return Create(TensorPrimitives.TanPi, Tensor.TanPi); - yield return Create(TensorPrimitives.Truncate, Tensor.Truncate); - yield return Create(TensorPrimitives.ILogB, Tensor.ILogB); - yield return Create(TensorPrimitives.ConvertChecked, Tensor.ConvertChecked); - yield return Create(TensorPrimitives.ConvertSaturating, Tensor.ConvertSaturating); - yield return Create(TensorPrimitives.ConvertTruncating, Tensor.ConvertTruncating); + yield return Create(float.Abs, Tensor.Abs); + yield return Create(float.Acos, Tensor.Acos); + yield return Create(float.Acosh, Tensor.Acosh); + yield return Create(float.AcosPi, Tensor.AcosPi); + yield return Create(float.Asin, Tensor.Asin); + yield return Create(float.Asinh, Tensor.Asinh); + yield return Create(float.AsinPi, Tensor.AsinPi); + yield return Create(float.Atan, Tensor.Atan); + yield return Create(float.Atanh, Tensor.Atanh); + yield return Create(float.AtanPi, Tensor.AtanPi); + yield return Create(float.Cbrt, Tensor.Cbrt); + yield return Create(float.Ceiling, Tensor.Ceiling); + yield return Create(float.Cos, Tensor.Cos); + yield return Create(float.Cosh, Tensor.Cosh); + yield return Create(float.CosPi, Tensor.CosPi); + yield return Create(float.DegreesToRadians, Tensor.DegreesToRadians); + yield return Create(float.Exp, Tensor.Exp); + yield return Create(float.Exp10, Tensor.Exp10); + yield return Create(float.Exp10M1, Tensor.Exp10M1); + yield return Create(float.Exp2, Tensor.Exp2); + yield return Create(float.Exp2M1, Tensor.Exp2M1); + yield return Create(float.ExpM1, Tensor.ExpM1); + yield return Create(float.Floor, Tensor.Floor); + yield return Create(int.LeadingZeroCount, Tensor.LeadingZeroCount); + yield return Create(float.Log, Tensor.Log); + yield return Create(float.Log10, Tensor.Log10); + yield return Create(float.Log10P1, Tensor.Log10P1); + yield return Create(float.Log2, Tensor.Log2); + yield return Create(float.Log2P1, Tensor.Log2P1); + yield return Create(float.LogP1, Tensor.LogP1); + yield return Create(x => -x, Tensor.Negate); + yield return Create(x => ~x, Tensor.OnesComplement); + yield return Create(int.PopCount, Tensor.PopCount); + yield return Create(float.RadiansToDegrees, Tensor.RadiansToDegrees); + yield return Create(f => 1 / f, Tensor.Reciprocal); + yield return Create(float.Round, Tensor.Round); + //yield return Create(float.Sigmoid, Tensor.Sigmoid); + yield return Create(float.Sin, Tensor.Sin); + yield return Create(float.Sinh, Tensor.Sinh); + yield return Create(float.SinPi, Tensor.SinPi); + //yield return Create(float.SoftMax, Tensor.SoftMax); + yield return Create(float.Sqrt, Tensor.Sqrt); + yield return Create(float.Tan, Tensor.Tan); + yield return Create(float.Tanh, Tensor.Tanh); + yield return Create(float.TanPi, Tensor.TanPi); + yield return Create(float.Truncate, Tensor.Truncate); + yield return Create(float.ILogB, Tensor.ILogB); + yield return Create(x => Expression.Lambda>(Expression.ConvertChecked(Expression.Constant(x), typeof(int))).Compile()(), Tensor.ConvertChecked); + yield return Create(x => (int)x, Tensor.ConvertSaturating); + yield return Create(x => (int)MathF.Truncate(x), Tensor.ConvertTruncating); static object[] Create(TensorPrimitivesSpanInSpanOut tensorPrimitivesMethod, TensorSpanInSpanOut tensorOperation) => new object[] { tensorPrimitivesMethod, tensorOperation }; @@ -106,26 +106,21 @@ public void TensorExtensionsSpanInSpanOut(TensorPrimitivesSpanInSpanO TIn[] data = new TIn[length]; TOut[] data2 = new TOut[length]; - TOut[] expectedOutput = new TOut[length]; FillTensor(data); TensorSpan x = Tensor.Create(data, tensorLength, []); TensorSpan destination = Tensor.Create(data2, tensorLength, []); - tensorPrimitivesOperation((ReadOnlySpan)data, expectedOutput); TensorSpan tensorResults = tensorOperation(x, destination); Assert.Equal(tensorLength, tensorResults.Lengths); nint[] startingIndex = new nint[tensorLength.Length]; // the "Return" value - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensorResults[startingIndex], (int)length); - // the "destination" value - ReadOnlySpan destSpan = MemoryMarshal.CreateSpan(ref destination[startingIndex], (int)length); + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref tensorResults.GetPinnableReference(), (int)length); for (int i = 0; i < data.Length; i++) { - Assert.Equal(expectedOutput[i], span[i]); - Assert.Equal(expectedOutput[i], destSpan[i]); + Assert.Equal(tensorPrimitivesOperation(data[i]), span[i]); } // Now test if the source is sliced to be smaller then the destination that the destination is also sliced @@ -134,79 +129,23 @@ public void TensorExtensionsSpanInSpanOut(TensorPrimitivesSpanInSpanO nint sliceFlattenedLength = CalculateTotalLength(Helpers.TensorSliceShapes[index]); x = x.Slice(sliceLengths); TIn[] sliceData = new TIn[sliceFlattenedLength]; - x.FlattenTo(sliceData); - expectedOutput = new TOut[sliceFlattenedLength]; - - if (TensorHelpers.IsContiguousAndDense(x)) - { - tensorPrimitivesOperation((ReadOnlySpan)sliceData, expectedOutput); - } - else - { - int rowLength = (int)Helpers.TensorSliceShapes[index][^1]; - for (int i = 0; i < sliceData.Length; i+= rowLength) - { - tensorPrimitivesOperation(((ReadOnlySpan)sliceData).Slice(i, rowLength), ((Span)expectedOutput).Slice(i, rowLength)); - } - - } - - tensorResults = tensorOperation(x, destination); - - // tensorResults lengths will still be the original tensorLength and not equal to the sliced length since that happened internally/automatically - Assert.Equal(tensorLength, tensorResults.Lengths); - - TensorSpan.Enumerator destEnum = destination.Slice(sliceLengths).GetEnumerator(); - TensorSpan.Enumerator tensorResultsEnum = tensorResults.Slice(sliceLengths).GetEnumerator(); - bool destEnumMove; - bool tensorResultsEnumMove; - - for (int i = 0; i < expectedOutput.Length; i++) - { - destEnumMove = destEnum.MoveNext(); - tensorResultsEnumMove = tensorResultsEnum.MoveNext(); - - Assert.True(destEnumMove); - Assert.True(tensorResultsEnumMove); - Assert.Equal(expectedOutput[i], destEnum.Current); - Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); - } // Now test if the source and destination are sliced (so neither is continuous) it works correctly. destination = destination.Slice(sliceLengths); x.FlattenTo(sliceData); - expectedOutput = new TOut[sliceFlattenedLength]; - - if (TensorHelpers.IsContiguousAndDense(x)) - { - tensorPrimitivesOperation((ReadOnlySpan)sliceData, expectedOutput); - } - else - { - int rowLength = (int)Helpers.TensorSliceShapes[index][^1]; - for (int i = 0; i < sliceData.Length; i += rowLength) - { - tensorPrimitivesOperation(((ReadOnlySpan)sliceData).Slice(i, rowLength), ((Span)expectedOutput).Slice(i, rowLength)); - } - - } tensorResults = tensorOperation(x, destination); Assert.Equal(Helpers.TensorSliceShapes[index], tensorResults.Lengths); - destEnum = destination.GetEnumerator(); - tensorResultsEnum = tensorResults.GetEnumerator(); - - for (int i = 0; i < expectedOutput.Length; i++) + TensorSpan.Enumerator tensorResultsEnum = tensorResults.GetEnumerator(); + bool tensorResultsEnumMove; + for (int i = 0; i < sliceData.Length; i++) { - destEnumMove = destEnum.MoveNext(); tensorResultsEnumMove = tensorResultsEnum.MoveNext(); - Assert.True(destEnumMove); Assert.True(tensorResultsEnumMove); - Assert.Equal(expectedOutput[i], destEnum.Current); - Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); + Assert.Equal(tensorPrimitivesOperation(sliceData[i]), tensorResultsEnum.Current); } }); } @@ -221,9 +160,33 @@ public static IEnumerable SpanInFloatOutData() yield return Create(TensorPrimitives.Min, Tensor.Min); yield return Create(TensorPrimitives.MinMagnitude, Tensor.MinMagnitude); yield return Create(TensorPrimitives.MinNumber, Tensor.MinNumber); - yield return Create(TensorPrimitives.Norm, Tensor.Norm); - yield return Create(TensorPrimitives.Product, Tensor.Product); - yield return Create(TensorPrimitives.Sum, Tensor.Sum); + yield return Create(x => + { + float sum = 0; + for (int i = 0; i < x.Length; i++) + { + sum += x[i] * x[i]; + } + return float.Sqrt(sum); + }, Tensor.Norm); + yield return Create(x => + { + float sum = 1; + for (int i = 0; i < x.Length; i++) + { + sum *= x[i]; + } + return sum; + }, Tensor.Product); + yield return Create(x => + { + float sum = 0; + for (int i = 0; i < x.Length; i++) + { + sum += x[i]; + } + return sum; + }, Tensor.Sum); static object[] Create(TensorPrimitivesSpanInTOut tensorPrimitivesMethod, TensorSpanInTOut tensorOperation) => new object[] { tensorPrimitivesMethod, tensorOperation }; @@ -269,21 +232,21 @@ public void TensorExtensionsSpanInTOut(TensorPrimitivesSpanInTOut tensorPr }); } - public delegate void TensorPrimitivesTwoSpanInSpanOut(ReadOnlySpan input, ReadOnlySpan inputTwo, Span output); + public delegate T TensorPrimitivesTwoSpanInSpanOut(T input, T inputTwo); public delegate ref readonly TensorSpan TensorTwoSpanInSpanOut(scoped in ReadOnlyTensorSpan input, scoped in ReadOnlyTensorSpan inputTwo, in TensorSpan destination); public delegate ref readonly TensorSpan TensorTwoSpanInSpanOutInPlace(in TensorSpan input, scoped in ReadOnlyTensorSpan inputTwo); public static IEnumerable TwoSpanInSpanOutData() { - yield return Create(TensorPrimitives.Add, Tensor.Add); - yield return Create(TensorPrimitives.Atan2, Tensor.Atan2); - yield return Create(TensorPrimitives.Atan2Pi, Tensor.Atan2Pi); - yield return Create(TensorPrimitives.CopySign, Tensor.CopySign); - yield return Create(TensorPrimitives.Divide, Tensor.Divide); - yield return Create(TensorPrimitives.Hypot, Tensor.Hypot); - yield return Create(TensorPrimitives.Ieee754Remainder, Tensor.Ieee754Remainder); - yield return Create(TensorPrimitives.Multiply, Tensor.Multiply); - yield return Create(TensorPrimitives.Pow, Tensor.Pow); - yield return Create(TensorPrimitives.Subtract, Tensor.Subtract); + yield return Create((x, y) => x + y, Tensor.Add); + yield return Create((x, y) => float.Atan2(x, y), Tensor.Atan2); + yield return Create((x, y) => float.Atan2Pi(x, y), Tensor.Atan2Pi); + yield return Create((x, y) => float.CopySign(x, y), Tensor.CopySign); + yield return Create((x, y) => x / y, Tensor.Divide); + yield return Create((x, y) => float.Hypot(x, y), Tensor.Hypot); + yield return Create((x, y) => float.Ieee754Remainder(x, y), Tensor.Ieee754Remainder); + yield return Create((x, y) => x * y, Tensor.Multiply); + yield return Create((x, y) => float.Pow(x, y), Tensor.Pow); + yield return Create((x, y) => x - y, Tensor.Subtract); static object[] Create(TensorPrimitivesTwoSpanInSpanOut tensorPrimitivesMethod, TensorTwoSpanInSpanOut tensorOperation) => new object[] { tensorPrimitivesMethod, tensorOperation }; @@ -309,162 +272,67 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut TensorSpan x = Tensor.Create(data1, tensorLengths, []); TensorSpan y = Tensor.Create(data2, tensorLengths, []); TensorSpan destination = Tensor.Create(destData, tensorLengths, []); - tensorPrimitivesOperation((ReadOnlySpan)data1, data2, expectedOutput); TensorSpan results = tensorOperation(x, y, destination); Assert.Equal(tensorLengths, results.Lengths); nint[] startingIndex = new nint[tensorLengths.Length]; // the "Return" value - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref results[startingIndex], (int)length); - // the "destination" value - ReadOnlySpan destSpan = MemoryMarshal.CreateSpan(ref destination[startingIndex], (int)length); + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref results.GetPinnableReference(), (int)length); for (int i = 0; i < data1.Length; i++) { - Assert.Equal(expectedOutput[i], span[i]); - Assert.Equal(expectedOutput[i], destSpan[i]); + Assert.Equal(tensorPrimitivesOperation(data1[i], data2[i]), span[i]); } - // Now test when both sources are exact sizes but destination is too large and gets sliced internally. - nint[] tempLengths = tensorLengths.Select(i => i + 1).ToArray(); - T[] tempDestData = new T[CalculateTotalLength(tempLengths)]; - destination = Tensor.Create(tempDestData, tempLengths, []); - results = tensorOperation(x, y, destination); - - // Since the slice was internal the result lengths will be the extra large size. - Assert.Equal(tempLengths, results.Lengths); - startingIndex = new nint[tensorLengths.Length]; - - TensorSpan.Enumerator destEnum = destination.Slice(tensorLengths).GetEnumerator(); - TensorSpan.Enumerator tensorResultsEnum = results.Slice(tensorLengths).GetEnumerator(); - bool destEnumMove; - bool tensorResultsEnumMove; - - for (int i = 0; i < expectedOutput.Length; i++) - { - destEnumMove = destEnum.MoveNext(); - tensorResultsEnumMove = tensorResultsEnum.MoveNext(); - - Assert.True(destEnumMove); - Assert.True(tensorResultsEnumMove); - Assert.Equal(expectedOutput[i], destEnum.Current); - Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); - } // Now test if the first source is sliced to be smaller than the second (but is broadcast compatible) that broadcasting happens). int rowLength = (int)Helpers.TensorSliceShapesForBroadcast[index][^1]; NRange[] sliceLengths = Helpers.TensorSliceShapesForBroadcast[index].Select(i => new NRange(0, i)).ToArray(); nint sliceFlattenedLength = CalculateTotalLength(Helpers.TensorSliceShapesForBroadcast[index]); - destination = destination.Slice(tensorLengths); + //destination = destination.Slice(sliceLengths); x.Slice(sliceLengths).BroadcastTo(x); x.FlattenTo(data1); - if (TensorHelpers.IsContiguousAndDense(x.Slice(sliceLengths)) && TensorHelpers.IsContiguousAndDense(y)) - { - tensorPrimitivesOperation((ReadOnlySpan)data1, data2, expectedOutput); - } - else - { - for (int i = 0; i < data1.Length; i += rowLength) - { - tensorPrimitivesOperation(((ReadOnlySpan)data1).Slice(i, rowLength), ((ReadOnlySpan)data2).Slice(i, rowLength), ((Span)expectedOutput).Slice(i, rowLength)); - } - - } - results = tensorOperation(x.Slice(sliceLengths), y, destination); // results lengths will still be the original tensorLength Assert.Equal(tensorLengths, results.Lengths); - destEnum = destination.GetEnumerator(); - tensorResultsEnum = results.GetEnumerator(); + TensorSpan.Enumerator tensorResultsEnum = results.GetEnumerator(); + TensorSpan.Enumerator xEnum = x.GetEnumerator(); + TensorSpan.Enumerator yEnum = y.GetEnumerator(); + bool tensorResultsEnumMove; - for (int i = 0; i < expectedOutput.Length; i++) + for (int i = 0; i < results.FlattenedLength; i++) { - destEnumMove = destEnum.MoveNext(); tensorResultsEnumMove = tensorResultsEnum.MoveNext(); - - Assert.True(destEnumMove); + xEnum.MoveNext(); + yEnum.MoveNext(); Assert.True(tensorResultsEnumMove); - Assert.Equal(expectedOutput[i], destEnum.Current); - Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); + + Assert.Equal(tensorPrimitivesOperation(xEnum.Current, yEnum.Current), tensorResultsEnum.Current); } // Now test if the second source is sliced to be smaller than the first (but is broadcast compatible) that broadcasting happens). y.Slice(sliceLengths).BroadcastTo(y); y.FlattenTo(data2); - if (TensorHelpers.IsContiguousAndDense(x) && TensorHelpers.IsContiguousAndDense(y.Slice(sliceLengths))) - { - tensorPrimitivesOperation((ReadOnlySpan)data1, data2, expectedOutput); - } - else - { - for (int i = 0; i < data2.Length; i += rowLength) - { - tensorPrimitivesOperation(((ReadOnlySpan)data1).Slice(i, rowLength), ((ReadOnlySpan)data2).Slice(i, rowLength), ((Span)expectedOutput).Slice(i, rowLength)); - } - - } - results = tensorOperation(x, y.Slice(sliceLengths), destination); // results lengths will still be the original tensorLength Assert.Equal(tensorLengths, results.Lengths); - destEnum = destination.GetEnumerator(); tensorResultsEnum = results.GetEnumerator(); - - for (int i = 0; i < expectedOutput.Length; i++) - { - destEnumMove = destEnum.MoveNext(); - tensorResultsEnumMove = tensorResultsEnum.MoveNext(); - - Assert.True(destEnumMove); - Assert.True(tensorResultsEnumMove); - Assert.Equal(expectedOutput[i], destEnum.Current); - Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); - } - - // Now test if both sources are sliced to be smaller than the destination that the destination will be sliced automatically - T[] sliceData1 = new T[sliceFlattenedLength]; - T[] sliceData2 = new T[sliceFlattenedLength]; - expectedOutput = new T[sliceFlattenedLength]; - - x.Slice(sliceLengths).FlattenTo(sliceData1); - y.Slice(sliceLengths).FlattenTo(sliceData2); - - if (TensorHelpers.IsContiguousAndDense(x.Slice(sliceLengths)) && TensorHelpers.IsContiguousAndDense(y.Slice(sliceLengths))) - { - tensorPrimitivesOperation((ReadOnlySpan)sliceData1, sliceData2, expectedOutput); - } - else - { - for (int i = 0; i < sliceData1.Length; i += rowLength) - { - tensorPrimitivesOperation(((ReadOnlySpan)sliceData1).Slice(i, rowLength), ((ReadOnlySpan)sliceData2).Slice(i, rowLength), ((Span)expectedOutput).Slice(i, rowLength)); - } - - } - - results = tensorOperation(x.Slice(sliceLengths), y.Slice(sliceLengths), destination); - - Assert.Equal(tensorLengths, results.Lengths); - - destEnum = destination.Slice(sliceLengths).GetEnumerator(); - tensorResultsEnum = results.Slice(sliceLengths).GetEnumerator(); - - for (int i = 0; i < expectedOutput.Length; i++) + xEnum = x.GetEnumerator(); + yEnum = y.GetEnumerator(); + for (int i = 0; i < results.FlattenedLength; i++) { - destEnumMove = destEnum.MoveNext(); tensorResultsEnumMove = tensorResultsEnum.MoveNext(); - - Assert.True(destEnumMove); + xEnum.MoveNext(); + yEnum.MoveNext(); Assert.True(tensorResultsEnumMove); - Assert.Equal(expectedOutput[i], destEnum.Current); - Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); + Assert.Equal(tensorPrimitivesOperation(xEnum.Current, yEnum.Current), tensorResultsEnum.Current); } }); } @@ -551,7 +419,7 @@ public static unsafe void TensorSpanSetSliceTests() { var ab = new TensorSpan(array: [0d, 1, 2, 3, 0d, 1, 2, 3]); // [0, 1, 2, 3] var b = ab.Reshape(lengths: new IntPtr[] { 2, 2, 2 }); // [[0, 1], [2, 3]] - var c = b.Slice(ranges: new NRange[] { 1.., 1..2, ..1 }); // [[0], [2]] + var c = b.Slice(new NRange[] { 1.., 1..2, ..1 }); // [[0], [2]] c.Reshape(lengths: new IntPtr[] { 1, 2, 1 }); }); @@ -775,7 +643,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => + Assert.Throws(() => { var spanInt = new TensorSpan(a, (int[])[], [2, 2], [1, 1]); }); @@ -790,34 +658,34 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); - //Make sure it works with NIndex - spanInt = new TensorSpan(a, (NIndex[])[1, 1], [2, 2], [0, 0]); - Assert.Equal(2, spanInt.Rank); - Assert.Equal(2, spanInt.Lengths[0]); - Assert.Equal(2, spanInt.Lengths[1]); - Assert.Equal(94, spanInt[0, 0]); - Assert.Equal(94, spanInt[0, 1]); - Assert.Equal(94, spanInt[1, 0]); - Assert.Equal(94, spanInt[1, 1]); - - //Make sure it works with NIndex - spanInt = new TensorSpan(a, (NIndex[])[^1, ^1], [2, 2], [0, 0]); - Assert.Equal(2, spanInt.Rank); - Assert.Equal(2, spanInt.Lengths[0]); - Assert.Equal(2, spanInt.Lengths[1]); - Assert.Equal(94, spanInt[0, 0]); - Assert.Equal(94, spanInt[0, 1]); - Assert.Equal(94, spanInt[1, 0]); - Assert.Equal(94, spanInt[1, 1]); + // // TODO: Make sure it works with NIndex + // spanInt = new TensorSpan(a, [0, 0], (NIndex[])[1, 1], [2, 2]); + // Assert.Equal(2, spanInt.Rank); + // Assert.Equal(2, spanInt.Lengths[0]); + // Assert.Equal(2, spanInt.Lengths[1]); + // Assert.Equal(94, spanInt[0, 0]); + // Assert.Equal(94, spanInt[0, 1]); + // Assert.Equal(94, spanInt[1, 0]); + // Assert.Equal(94, spanInt[1, 1]); + + // // TODO: Make sure it works with NIndex + // spanInt = new TensorSpan(a, [0, 0], (NIndex[])[^1, ^1], [2, 2]); + // Assert.Equal(2, spanInt.Rank); + // Assert.Equal(2, spanInt.Lengths[0]); + // Assert.Equal(2, spanInt.Lengths[1]); + // Assert.Equal(94, spanInt[0, 0]); + // Assert.Equal(94, spanInt[0, 1]); + // Assert.Equal(94, spanInt[1, 0]); + // Assert.Equal(94, spanInt[1, 1]); } [Fact] public static void TensorSpanArrayConstructorTests() { // Make sure exception is thrown if lengths and strides would let you go past the end of the array - Assert.Throws(() => new TensorSpan(new double[0], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); - Assert.Throws(() => new TensorSpan(new double[1], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); - Assert.Throws(() => new TensorSpan(new double[2], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 2 })); + Assert.Throws(() => new TensorSpan(new double[0], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); + Assert.Throws(() => new TensorSpan(new double[1], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); + Assert.Throws(() => new TensorSpan(new double[2], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 2 })); // Make sure basic T[] constructor works int[] a = { 91, 92, -93, 94 }; @@ -843,7 +711,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(1, spanInt.Rank); Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); - Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(1, spanInt.Strides[0]); // Make sure it still throws on index 0 Assert.Throws(() => { var spanInt = new TensorSpan(b); @@ -859,7 +727,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Strides[0]); // Make sure 2D array works - spanInt = new TensorSpan(a, new Index(0), [2, 2], default); + spanInt = new TensorSpan(a, 0, [2, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -869,24 +737,24 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[1, 1]); // Make sure can use only some of the array - spanInt = new TensorSpan(a, new Index(0), [1, 2], default); + spanInt = new TensorSpan(a, 0, [1, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); Assert.Throws(() => { - var spanInt = new TensorSpan(a, new Index(0), [1, 2], default); + var spanInt = new TensorSpan(a, 0, [1, 2], default); var x = spanInt[1, 1]; }); Assert.Throws(() => { - var spanInt = new TensorSpan(a, new Index(0), [1, 2], default); + var spanInt = new TensorSpan(a, 0, [1, 2], default); var x = spanInt[1, 0]; }); // Make sure Index offset works correctly - spanInt = new TensorSpan(a, new Index(1), [1, 2], default); + spanInt = new TensorSpan(a, 1, [1, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -894,7 +762,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(-93, spanInt[0, 1]); // Make sure Index offset works correctly - spanInt = new TensorSpan(a, new Index(2), [1, 2], default); + spanInt = new TensorSpan(a, 2, [1, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -902,12 +770,12 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); // Make sure we catch that there aren't enough elements in the array for the lengths - Assert.Throws(() => { - var spanInt = new TensorSpan(a, new Index(3), [1, 2], default); + Assert.Throws(() => { + var spanInt = new TensorSpan(a, 3, [1, 2], default); }); // Make sure 2D array works with basic strides - spanInt = new TensorSpan(a, new Index(0), [2, 2], [2, 1]); + spanInt = new TensorSpan(a, 0, [2, 2], [2, 1]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -917,7 +785,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[1, 1]); // Make sure 2D array works with stride of 0 to loop over first 2 elements again - spanInt = new TensorSpan(a, new Index(0), [2, 2], [0, 1]); + spanInt = new TensorSpan(a, 0, [2, 2], [0, 1]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -927,7 +795,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[1, 1]); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again - spanInt = new TensorSpan(a, new Index(2), [2, 2], [0, 1]); + spanInt = new TensorSpan(a, 2, [2, 2], [0, 1]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -937,7 +805,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[1, 1]); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again - spanInt = new TensorSpan(a, new Index(3), [2, 2], [0, 0]); + spanInt = new TensorSpan(a, 3, [2, 2], [0, 0]); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -948,18 +816,18 @@ public static void TensorSpanArrayConstructorTests() // Make sure strides can't be negative Assert.Throws(() => { - var spanInt = new TensorSpan(a, new Index(3), [1, 2], [-1, 0]); + var spanInt = new TensorSpan(a, 3, [1, 2], [-1, 0]); }); Assert.Throws(() => { - var spanInt = new TensorSpan(a, new Index(3), [1, 2], [0, -1]); + var spanInt = new TensorSpan(a, 3, [1, 2], [0, -1]); }); // Make sure lengths can't be negative Assert.Throws(() => { - var spanInt = new TensorSpan(a, new Index(3), [-1, 2], []); + var spanInt = new TensorSpan(a, 3, [-1, 2], []); }); Assert.Throws(() => { - var spanInt = new TensorSpan(a, new Index(3), [1, -2], []); + var spanInt = new TensorSpan(a, 3, [1, -2], []); }); // Make sure 2D array works with strides to hit element 0,0,2,2 @@ -973,7 +841,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => { + Assert.Throws(() => { var spanInt = new TensorSpan(a, 0, [2, 2], [1, 1]); }); } @@ -995,12 +863,12 @@ public static void TensorSpanSpanConstructorTests() // Should be a Tensor with 0 elements but Rank 1 with dimension 0 length 0 Span b = []; spanInt = new TensorSpan(b); - Assert.Equal(1, spanInt.Rank); - Assert.Equal(0, spanInt.Lengths[0]); + Assert.Equal(0, spanInt.Rank); + Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.FlattenedLength); - Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides.Length); // Make sure it still throws on index 0 - Assert.Throws(() => { + Assert.Throws(() => { Span b = []; var spanInt = new TensorSpan(b); var x = spanInt[0]; @@ -1052,7 +920,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(94, spanInt[0, 1]); // Make sure we catch that there aren't enough elements in the array for the lengths - Assert.Throws(() => { + Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new TensorSpan(a.Slice(3), [1, 2], default); }); @@ -1128,7 +996,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => { + Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new TensorSpan(a, [2, 2], [1, 1]); }); @@ -1157,12 +1025,12 @@ public static unsafe void TensorSpanPointerConstructorTests() fixed (int* p = b) { spanInt = new TensorSpan(p, 0); - Assert.Equal(1, spanInt.Rank); - Assert.Equal(0, spanInt.Lengths[0]); + Assert.Equal(0, spanInt.Rank); + Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.FlattenedLength); - Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides.Length); // Make sure it still throws on index 0 - Assert.Throws(() => + Assert.Throws(() => { Span b = []; fixed (int* p = b) @@ -1229,7 +1097,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(94, spanInt[0, 1]); // Make sure we catch that there aren't enough elements in the array for the lengths - Assert.Throws(() => + Assert.Throws(() => { Span a = [91, 92, -93, 94]; fixed (int* p = a) @@ -1335,7 +1203,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(-93, spanInt[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => + Assert.Throws(() => { Span a = [91, 92, -93, 94]; fixed (int* p = a) @@ -1351,7 +1219,7 @@ public static void TensorSpanLargeDimensionsTests() { int[] a = { 91, 92, -93, 94, 95, -96 }; int[] results = new int[6]; - TensorSpan spanInt = a.AsTensorSpan(1, 1, 1, 1, 1, 6); + TensorSpan spanInt = a.AsTensorSpan([1, 1, 1, 1, 1, 6]); Assert.Equal(6, spanInt.Rank); Assert.Equal(6, spanInt.Lengths.Length); @@ -1362,11 +1230,11 @@ public static void TensorSpanLargeDimensionsTests() Assert.Equal(1, spanInt.Lengths[4]); Assert.Equal(6, spanInt.Lengths[5]); Assert.Equal(6, spanInt.Strides.Length); - Assert.Equal(6, spanInt.Strides[0]); - Assert.Equal(6, spanInt.Strides[1]); - Assert.Equal(6, spanInt.Strides[2]); - Assert.Equal(6, spanInt.Strides[3]); - Assert.Equal(6, spanInt.Strides[4]); + Assert.Equal(0, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides[1]); + Assert.Equal(0, spanInt.Strides[2]); + Assert.Equal(0, spanInt.Strides[3]); + Assert.Equal(0, spanInt.Strides[4]); Assert.Equal(1, spanInt.Strides[5]); Assert.Equal(91, spanInt[0, 0, 0, 0, 0, 0]); Assert.Equal(92, spanInt[0, 0, 0, 0, 0, 1]); @@ -1379,7 +1247,7 @@ public static void TensorSpanLargeDimensionsTests() a = [91, 92, -93, 94, 95, -96, -91, -92, 93, -94, -95, 96]; results = new int[12]; - spanInt = a.AsTensorSpan(1, 2, 2, 1, 1, 3); + spanInt = a.AsTensorSpan([1, 2, 2, 1, 1, 3]); Assert.Equal(6, spanInt.Lengths.Length); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -1388,11 +1256,11 @@ public static void TensorSpanLargeDimensionsTests() Assert.Equal(1, spanInt.Lengths[4]); Assert.Equal(3, spanInt.Lengths[5]); Assert.Equal(6, spanInt.Strides.Length); - Assert.Equal(12, spanInt.Strides[0]); + Assert.Equal(0, spanInt.Strides[0]); Assert.Equal(6, spanInt.Strides[1]); Assert.Equal(3, spanInt.Strides[2]); - Assert.Equal(3, spanInt.Strides[3]); - Assert.Equal(3, spanInt.Strides[4]); + Assert.Equal(0, spanInt.Strides[3]); + Assert.Equal(0, spanInt.Strides[4]); Assert.Equal(1, spanInt.Strides[5]); Assert.Equal(91, spanInt[0, 0, 0, 0, 0, 0]); Assert.Equal(92, spanInt[0, 0, 0, 0, 0, 1]); @@ -1415,7 +1283,7 @@ public static void IntArrayAsTensorSpan() { int[] a = { 91, 92, -93, 94 }; int[] results = new int[4]; - TensorSpan spanInt = a.AsTensorSpan(4); + TensorSpan spanInt = a.AsTensorSpan([4]); Assert.Equal(1, spanInt.Rank); Assert.Equal(1, spanInt.Lengths.Length); @@ -1442,7 +1310,7 @@ public static void IntArrayAsTensorSpan() a[1] = 92; a[2] = -93; a[3] = 94; - spanInt = a.AsTensorSpan(2, 2); + spanInt = a.AsTensorSpan([2, 2]); spanInt.FlattenTo(results); Assert.Equal(a, results); Assert.Equal(2, spanInt.Rank); @@ -1472,7 +1340,7 @@ public static void IntArrayAsTensorSpan() public static void TensorSpanFillTest() { int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - TensorSpan spanInt = a.AsTensorSpan(3, 3); + TensorSpan spanInt = a.AsTensorSpan([3, 3]); spanInt.Fill(-1); var enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1495,7 +1363,7 @@ public static void TensorSpanFillTest() } a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - spanInt = a.AsTensorSpan(9); + spanInt = a.AsTensorSpan([9]); spanInt.Fill(-1); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1504,7 +1372,7 @@ public static void TensorSpanFillTest() } a = [.. Enumerable.Range(0, 27)]; - spanInt = a.AsTensorSpan(3,3,3); + spanInt = a.AsTensorSpan([3,3,3]); spanInt.Fill(-1); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1513,7 +1381,7 @@ public static void TensorSpanFillTest() } a = [.. Enumerable.Range(0, 12)]; - spanInt = a.AsTensorSpan(3, 2, 2); + spanInt = a.AsTensorSpan([3, 2, 2]); spanInt.Fill(-1); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1522,7 +1390,7 @@ public static void TensorSpanFillTest() } a = [.. Enumerable.Range(0, 16)]; - spanInt = a.AsTensorSpan(2,2,2,2); + spanInt = a.AsTensorSpan([2,2,2,2]); spanInt.Fill(-1); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1531,7 +1399,7 @@ public static void TensorSpanFillTest() } a = [.. Enumerable.Range(0, 24)]; - spanInt = a.AsTensorSpan(3, 2, 2, 2); + spanInt = a.AsTensorSpan([3, 2, 2, 2]); spanInt.Fill(-1); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1544,7 +1412,7 @@ public static void TensorSpanFillTest() public static void TensorSpanClearTest() { int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - TensorSpan spanInt = a.AsTensorSpan(3, 3); + TensorSpan spanInt = a.AsTensorSpan([3, 3]); var slice = spanInt.Slice(0..2, 0..2); slice.Clear(); @@ -1573,7 +1441,7 @@ public static void TensorSpanClearTest() } a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - spanInt = a.AsTensorSpan(9); + spanInt = a.AsTensorSpan([9]); slice = spanInt.Slice(0..1); slice.Clear(); Assert.Equal(0, slice[0]); @@ -1598,7 +1466,7 @@ public static void TensorSpanClearTest() } a = [.. Enumerable.Range(0, 27)]; - spanInt = a.AsTensorSpan(3, 3, 3); + spanInt = a.AsTensorSpan([3, 3, 3]); spanInt.Clear(); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1607,7 +1475,7 @@ public static void TensorSpanClearTest() } a = [.. Enumerable.Range(0, 12)]; - spanInt = a.AsTensorSpan(3, 2, 2); + spanInt = a.AsTensorSpan([3, 2, 2]); spanInt.Clear(); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1616,7 +1484,7 @@ public static void TensorSpanClearTest() } a = [.. Enumerable.Range(0, 16)]; - spanInt = a.AsTensorSpan(2, 2, 2, 2); + spanInt = a.AsTensorSpan([2, 2, 2, 2]); spanInt.Clear(); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1625,7 +1493,7 @@ public static void TensorSpanClearTest() } a = [.. Enumerable.Range(0, 24)]; - spanInt = a.AsTensorSpan(3, 2, 2, 2); + spanInt = a.AsTensorSpan([3, 2, 2, 2]); spanInt.Clear(); enumerator = spanInt.GetEnumerator(); while (enumerator.MoveNext()) @@ -1639,8 +1507,8 @@ public static void TensorSpanCopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; - TensorSpan leftSpan = leftData.AsTensorSpan(3, 3); - TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); + TensorSpan leftSpan = leftData.AsTensorSpan([3, 3]); + TensorSpan rightSpan = rightData.AsTensorSpan([3, 3]); leftSpan.CopyTo(rightSpan); var leftEnum = leftSpan.GetEnumerator(); var rightEnum = rightSpan.GetEnumerator(); @@ -1658,16 +1526,16 @@ public static void TensorSpanCopyTest() { leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; rightData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - TensorSpan leftSpan = leftData.AsTensorSpan(9); - TensorSpan tensor = rightData.AsTensorSpan(rightData.Length); + TensorSpan leftSpan = leftData.AsTensorSpan([9]); + TensorSpan tensor = rightData.AsTensorSpan([rightData.Length]); leftSpan.CopyTo(tensor); } ); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; - leftSpan = leftData.AsTensorSpan(3, 3, 3); - rightSpan = rightData.AsTensorSpan(3, 3, 3); + leftSpan = leftData.AsTensorSpan([3, 3, 3]); + rightSpan = rightData.AsTensorSpan([3, 3, 3]); leftSpan.CopyTo(rightSpan); while (leftEnum.MoveNext() && rightEnum.MoveNext()) @@ -1677,7 +1545,7 @@ public static void TensorSpanCopyTest() Assert.Throws(() => { - var l = leftData.AsTensorSpan(3, 3, 3); + var l = leftData.AsTensorSpan([3, 3, 3]); var r = new TensorSpan(); l.CopyTo(r); }); @@ -1688,8 +1556,8 @@ public static void TensorSpanTryCopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; - TensorSpan leftSpan = leftData.AsTensorSpan(3, 3); - TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); + TensorSpan leftSpan = leftData.AsTensorSpan([3, 3]); + TensorSpan rightSpan = rightData.AsTensorSpan([3, 3]); var success = leftSpan.TryCopyTo(rightSpan); Assert.True(success); var leftEnum = leftSpan.GetEnumerator(); @@ -1705,16 +1573,16 @@ public static void TensorSpanTryCopyTest() leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; rightData = new int[15]; - leftSpan = leftData.AsTensorSpan(9); - rightSpan = rightData.AsTensorSpan(15); + leftSpan = leftData.AsTensorSpan([9]); + rightSpan = rightData.AsTensorSpan([15]); success = leftSpan.TryCopyTo(rightSpan); Assert.False(success); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; - leftSpan = leftData.AsTensorSpan(3, 3, 3); - rightSpan = rightData.AsTensorSpan(3, 3, 3); + leftSpan = leftData.AsTensorSpan([3, 3, 3]); + rightSpan = rightData.AsTensorSpan([3, 3, 3]); success = leftSpan.TryCopyTo(rightSpan); Assert.True(success); @@ -1723,7 +1591,7 @@ public static void TensorSpanTryCopyTest() Assert.Equal(leftEnum.Current, rightEnum.Current); } - var l = leftData.AsTensorSpan(3, 3, 3); + var l = leftData.AsTensorSpan([3, 3, 3]); var r = new TensorSpan(); success = l.TryCopyTo(r); Assert.False(success); @@ -1783,11 +1651,11 @@ public static void TensorSpanSliceTest() int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; - TensorSpan spanInt = a.AsTensorSpan(3, 3); + TensorSpan spanInt = a.AsTensorSpan([3, 3]); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1)); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(1..2)); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1, 5..6)); + Assert.Throws(() => a.AsTensorSpan([2, 3]).Slice(0..1)); + Assert.Throws(() => a.AsTensorSpan([2, 3]).Slice(1..2)); + Assert.Throws(() => a.AsTensorSpan([2, 3]).Slice(0..1, 5..6)); var sp = spanInt.Slice(1..3, 1..3); Assert.Equal(5, sp[0, 0]); @@ -1827,7 +1695,7 @@ public static void TensorSpanSliceTest() sp = spanInt.Slice(0..1, 0..1); Assert.Equal(1, sp[0, 0]); - Assert.Throws(() => a.AsTensorSpan(3, 3).Slice(0..1, 0..1)[0, 1]); + Assert.Throws(() => a.AsTensorSpan([3, 3]).Slice(0..1, 0..1)[0, 1]); slice = [1]; results = new int[1]; sp.FlattenTo(results); @@ -1856,7 +1724,7 @@ public static void TensorSpanSliceTest() } int[] numbers = [.. Enumerable.Range(0, 27)]; - spanInt = numbers.AsTensorSpan(3, 3, 3); + spanInt = numbers.AsTensorSpan([3, 3, 3]); sp = spanInt.Slice(1..2, 1..2, 1..2); Assert.Equal(13, sp[0, 0, 0]); slice = [13]; @@ -1891,7 +1759,7 @@ public static void TensorSpanSliceTest() } numbers = [.. Enumerable.Range(0, 16)]; - spanInt = numbers.AsTensorSpan(2, 2, 2, 2); + spanInt = numbers.AsTensorSpan([2, 2, 2, 2]); sp = spanInt.Slice(1..2, 0..2, 1..2, 0..2); Assert.Equal(10, sp[0,0,0,0]); Assert.Equal(11, sp[0,0,0,1]); @@ -1913,7 +1781,7 @@ public static void TensorSpanSliceTest() public static void LongArrayAsTensorSpan() { long[] b = { 91, -92, 93, 94, -95 }; - TensorSpan spanLong = b.AsTensorSpan(5); + TensorSpan spanLong = b.AsTensorSpan([5]); Assert.Equal(91, spanLong[0]); Assert.Equal(-92, spanLong[1]); Assert.Equal(93, spanLong[2]); diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs index fce9d8f68f474f..a2e0f203f2b6b6 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Runtime.InteropServices; using Xunit; -using static System.Numerics.Tensors.Tests.TensorTests; namespace System.Numerics.Tensors.Tests { @@ -14,7 +13,7 @@ public class TensorTests { #region TensorPrimitivesForwardsTests private void FillTensor(Span span) - where T : INumberBase + where T : INumberBase, IComparisonOperators { for (int i = 0; i < span.Length; i++) { @@ -36,57 +35,57 @@ private static nint CalculateTotalLength(ReadOnlySpan lengths) } public delegate Tensor PerformSpanInSpanOut(in ReadOnlyTensorSpan input); - public delegate void PerformCalculationSpanInSpanOut(ReadOnlySpan input, Span output); + public delegate T PerformCalculationSpanInSpanOut(T input); public static IEnumerable SpanInSpanOutData() { - yield return Create(TensorPrimitives.Abs, Tensor.Abs); - yield return Create(TensorPrimitives.Acos, Tensor.Acos); - yield return Create(TensorPrimitives.Acosh, Tensor.Acosh); - yield return Create(TensorPrimitives.AcosPi, Tensor.AcosPi); - yield return Create(TensorPrimitives.Asin, Tensor.Asin); - yield return Create(TensorPrimitives.Asinh, Tensor.Asinh); - yield return Create(TensorPrimitives.AsinPi, Tensor.AsinPi); - yield return Create(TensorPrimitives.Atan, Tensor.Atan); - yield return Create(TensorPrimitives.Atanh, Tensor.Atanh); - yield return Create(TensorPrimitives.AtanPi, Tensor.AtanPi); - yield return Create(TensorPrimitives.Cbrt, Tensor.Cbrt); - yield return Create(TensorPrimitives.Ceiling, Tensor.Ceiling); - yield return Create(TensorPrimitives.Cos, Tensor.Cos); - yield return Create(TensorPrimitives.Cosh, Tensor.Cosh); - yield return Create(TensorPrimitives.CosPi, Tensor.CosPi); - yield return Create(TensorPrimitives.DegreesToRadians, Tensor.DegreesToRadians); - yield return Create(TensorPrimitives.Exp, Tensor.Exp); - yield return Create(TensorPrimitives.Exp10, Tensor.Exp10); - yield return Create(TensorPrimitives.Exp10M1, Tensor.Exp10M1); - yield return Create(TensorPrimitives.Exp2, Tensor.Exp2); - yield return Create(TensorPrimitives.Exp2M1, Tensor.Exp2M1); - yield return Create(TensorPrimitives.ExpM1, Tensor.ExpM1); - yield return Create(TensorPrimitives.Floor, Tensor.Floor); - yield return Create(TensorPrimitives.LeadingZeroCount, Tensor.LeadingZeroCount); - yield return Create(TensorPrimitives.LeadingZeroCount, Tensor.LeadingZeroCount); - yield return Create(TensorPrimitives.Log, Tensor.Log); - yield return Create(TensorPrimitives.Log10, Tensor.Log10); - yield return Create(TensorPrimitives.Log10P1, Tensor.Log10P1); - yield return Create(TensorPrimitives.Log2, Tensor.Log2); - yield return Create(TensorPrimitives.Log2P1, Tensor.Log2P1); - yield return Create(TensorPrimitives.LogP1, Tensor.LogP1); - yield return Create(TensorPrimitives.Negate, Tensor.Negate); - yield return Create(TensorPrimitives.OnesComplement, Tensor.OnesComplement); - yield return Create(TensorPrimitives.PopCount, Tensor.PopCount); - yield return Create(TensorPrimitives.RadiansToDegrees, Tensor.RadiansToDegrees); - yield return Create(TensorPrimitives.Reciprocal, Tensor.Reciprocal); - yield return Create(TensorPrimitives.Round, Tensor.Round); - yield return Create(TensorPrimitives.Sigmoid, Tensor.Sigmoid); - yield return Create(TensorPrimitives.Sin, Tensor.Sin); - yield return Create(TensorPrimitives.Sinh, Tensor.Sinh); - yield return Create(TensorPrimitives.SinPi, Tensor.SinPi); - yield return Create(TensorPrimitives.SoftMax, Tensor.SoftMax); - yield return Create(TensorPrimitives.Sqrt, Tensor.Sqrt); - yield return Create(TensorPrimitives.Tan, Tensor.Tan); - yield return Create(TensorPrimitives.Tanh, Tensor.Tanh); - yield return Create(TensorPrimitives.TanPi, Tensor.TanPi); - yield return Create(TensorPrimitives.Truncate, Tensor.Truncate); + yield return Create(float.Abs, Tensor.Abs); + yield return Create(float.Acos, Tensor.Acos); + yield return Create(float.Acosh, Tensor.Acosh); + yield return Create(float.AcosPi, Tensor.AcosPi); + yield return Create(float.Asin, Tensor.Asin); + yield return Create(float.Asinh, Tensor.Asinh); + yield return Create(float.AsinPi, Tensor.AsinPi); + yield return Create(float.Atan, Tensor.Atan); + yield return Create(float.Atanh, Tensor.Atanh); + yield return Create(float.AtanPi, Tensor.AtanPi); + yield return Create(float.Cbrt, Tensor.Cbrt); + yield return Create(float.Ceiling, Tensor.Ceiling); + yield return Create(float.Cos, Tensor.Cos); + yield return Create(float.Cosh, Tensor.Cosh); + yield return Create(float.CosPi, Tensor.CosPi); + yield return Create(float.DegreesToRadians, Tensor.DegreesToRadians); + yield return Create(float.Exp, Tensor.Exp); + yield return Create(float.Exp10, Tensor.Exp10); + yield return Create(float.Exp10M1, Tensor.Exp10M1); + yield return Create(float.Exp2, Tensor.Exp2); + yield return Create(float.Exp2M1, Tensor.Exp2M1); + yield return Create(float.ExpM1, Tensor.ExpM1); + yield return Create(float.Floor, Tensor.Floor); + yield return Create(int.LeadingZeroCount, Tensor.LeadingZeroCount); + yield return Create(int.LeadingZeroCount, Tensor.LeadingZeroCount); + yield return Create(float.Log, Tensor.Log); + yield return Create(float.Log10, Tensor.Log10); + yield return Create(float.Log10P1, Tensor.Log10P1); + yield return Create(float.Log2, Tensor.Log2); + yield return Create(float.Log2P1, Tensor.Log2P1); + yield return Create(float.LogP1, Tensor.LogP1); + yield return Create(f => -f, Tensor.Negate); + yield return Create(f => ~f, Tensor.OnesComplement); + yield return Create(int.PopCount, Tensor.PopCount); + yield return Create(float.RadiansToDegrees, Tensor.RadiansToDegrees); + yield return Create( f => 1 / f, Tensor.Reciprocal); + yield return Create(float.Round, Tensor.Round); + //yield return Create(float.Sigmoid, Tensor.Sigmoid); + yield return Create(float.Sin, Tensor.Sin); + yield return Create(float.Sinh, Tensor.Sinh); + yield return Create(float.SinPi, Tensor.SinPi); + //yield return Create(float.SoftMax, Tensor.SoftMax); + yield return Create(float.Sqrt, Tensor.Sqrt); + yield return Create(float.Tan, Tensor.Tan); + yield return Create(float.Tanh, Tensor.Tanh); + yield return Create(float.TanPi, Tensor.TanPi); + yield return Create(float.Truncate, Tensor.Truncate); static object[] Create(PerformCalculationSpanInSpanOut tensorPrimitivesMethod, PerformSpanInSpanOut tensorOperation) => new object[] { tensorPrimitivesMethod, tensorOperation }; @@ -94,7 +93,7 @@ static object[] Create(PerformCalculationSpanInSpanOut tensorPrimitivesMet [Theory, MemberData(nameof(SpanInSpanOutData))] public void TensorExtensionsSpanInSpanOut(PerformCalculationSpanInSpanOut tensorPrimitivesOperation, PerformSpanInSpanOut tensorOperation) - where T: INumberBase + where T: INumberBase, IComparisonOperators { Assert.All(Helpers.TensorShapes, tensorLength => { @@ -104,16 +103,15 @@ public void TensorExtensionsSpanInSpanOut(PerformCalculationSpanInSpanOut FillTensor(data); Tensor x = Tensor.Create(data, tensorLength, []); - tensorPrimitivesOperation((ReadOnlySpan)data, expectedOutput); + Tensor results = tensorOperation(x); Assert.Equal(tensorLength, results.Lengths); - nint[] startingIndex = new nint[tensorLength.Length]; - ReadOnlySpan span = MemoryMarshal.CreateSpan(ref results[startingIndex], (int)length); + ReadOnlySpan span = MemoryMarshal.CreateSpan(ref results.GetPinnableReference(), (int)length); for (int i = 0; i < data.Length; i++) { - Assert.Equal(expectedOutput[i], span[i]); + Assert.Equal(tensorPrimitivesOperation(data[i]), span[i]); } }); } @@ -128,9 +126,33 @@ public static IEnumerable SpanInFloatOutData() yield return Create(TensorPrimitives.Min, Tensor.Min); yield return Create(TensorPrimitives.MinMagnitude, Tensor.MinMagnitude); yield return Create(TensorPrimitives.MinNumber, Tensor.MinNumber); - yield return Create(TensorPrimitives.Norm, Tensor.Norm); - yield return Create(TensorPrimitives.Product, Tensor.Product); - yield return Create(TensorPrimitives.Sum, Tensor.Sum); + yield return Create(x => + { + float sum = 0; + for (int i = 0; i < x.Length; i++) + { + sum += x[i] * x[i]; + } + return float.Sqrt(sum); + }, Tensor.Norm); + yield return Create(x => + { + float sum = 1; + for (int i = 0; i < x.Length; i++) + { + sum *= x[i]; + } + return sum; + }, Tensor.Product); + yield return Create(x => + { + float sum = 0; + for (int i = 0; i < x.Length; i++) + { + sum+= x[i]; + } + return sum; + }, Tensor.Sum); static object[] Create(PerformCalculationSpanInTOut tensorPrimitivesMethod, PerformSpanInTOut tensorOperation) => new object[] { tensorPrimitivesMethod, tensorOperation }; @@ -138,7 +160,7 @@ static object[] Create(PerformCalculationSpanInTOut tensorPrimitivesMethod [Theory, MemberData(nameof(SpanInFloatOutData))] public void TensorExtensionsSpanInTOut(PerformCalculationSpanInTOut tensorPrimitivesOperation, PerformSpanInTOut tensorOperation) - where T : INumberBase + where T : INumberBase, IComparisonOperators { Assert.All(Helpers.TensorShapes, tensorLength => { @@ -175,7 +197,7 @@ static object[] Create(PerformCalculationTwoSpanInSpanOut tensorPrimitives [Theory, MemberData(nameof(TwoSpanInSpanOutData))] public void TensorExtensionsTwoSpanInSpanOut(PerformCalculationTwoSpanInSpanOut tensorPrimitivesOperation, PerformTwoSpanInSpanOut tensorOperation) - where T: INumberBase + where T: INumberBase, IComparisonOperators { Assert.All(Helpers.TensorShapes, tensorLength => { @@ -206,8 +228,24 @@ public void TensorExtensionsTwoSpanInSpanOut(PerformCalculationTwoSpanInSpanO public delegate T PerformCalculationTwoSpanInFloatOut(ReadOnlySpan input, ReadOnlySpan inputTwo); public static IEnumerable TwoSpanInFloatOutData() { - yield return Create(TensorPrimitives.Distance, Tensor.Distance); - yield return Create(TensorPrimitives.Dot, Tensor.Dot); + yield return Create((x, y) => + { + float sum = 0; + for (int i = 0; i < x.Length; i++) + { + sum += (x[i] - y[i]) * (x[i] - y[i]); + } + return float.Sqrt(sum); + }, Tensor.Distance); + yield return Create((x, y) => + { + float sum = 0; + for (int i = 0; i < x.Length; i++) + { + sum += x[i] * y[i]; + } + return sum; + }, Tensor.Dot); static object[] Create(PerformCalculationTwoSpanInFloatOut tensorPrimitivesMethod, PerformTwoSpanInFloatOut tensorOperation) => new object[] { tensorPrimitivesMethod, tensorOperation }; @@ -215,7 +253,7 @@ static object[] Create(PerformCalculationTwoSpanInFloatOut tensorPrimitive [Theory, MemberData(nameof(TwoSpanInFloatOutData))] public void TensorExtensionsTwoSpanInFloatOut(PerformCalculationTwoSpanInFloatOut tensorPrimitivesOperation, PerformTwoSpanInFloatOut tensorOperation) - where T: INumberBase + where T: INumberBase, IComparisonOperators { Assert.All(Helpers.TensorShapes, tensorLength => { @@ -241,7 +279,7 @@ public static void TensorLargeDimensionsTests() { int[] a = { 91, 92, -93, 94, 95, -96 }; int[] results = new int[6]; - Tensor tensor = Tensor.Create(a,[1, 1, 1, 1, 1, 6]); + Tensor tensor = Tensor.Create(a, lengths: [1, 1, 1, 1, 1, 6]); Assert.Equal(6, tensor.Rank); Assert.Equal(6, tensor.Lengths.Length); @@ -252,11 +290,11 @@ public static void TensorLargeDimensionsTests() Assert.Equal(1, tensor.Lengths[4]); Assert.Equal(6, tensor.Lengths[5]); Assert.Equal(6, tensor.Strides.Length); - Assert.Equal(6, tensor.Strides[0]); - Assert.Equal(6, tensor.Strides[1]); - Assert.Equal(6, tensor.Strides[2]); - Assert.Equal(6, tensor.Strides[3]); - Assert.Equal(6, tensor.Strides[4]); + Assert.Equal(0, tensor.Strides[0]); + Assert.Equal(0, tensor.Strides[1]); + Assert.Equal(0, tensor.Strides[2]); + Assert.Equal(0, tensor.Strides[3]); + Assert.Equal(0, tensor.Strides[4]); Assert.Equal(1, tensor.Strides[5]); Assert.Equal(91, tensor[0, 0, 0, 0, 0, 0]); Assert.Equal(92, tensor[0, 0, 0, 0, 0, 1]); @@ -269,7 +307,7 @@ public static void TensorLargeDimensionsTests() a = [91, 92, -93, 94, 95, -96, -91, -92, 93, -94, -95, 96]; results = new int[12]; - tensor = Tensor.Create(a, [1, 2, 2, 1, 1, 3]); + tensor = Tensor.Create(a, lengths: [1, 2, 2, 1, 1, 3]); Assert.Equal(6, tensor.Lengths.Length); Assert.Equal(1, tensor.Lengths[0]); Assert.Equal(2, tensor.Lengths[1]); @@ -278,11 +316,11 @@ public static void TensorLargeDimensionsTests() Assert.Equal(1, tensor.Lengths[4]); Assert.Equal(3, tensor.Lengths[5]); Assert.Equal(6, tensor.Strides.Length); - Assert.Equal(12, tensor.Strides[0]); + Assert.Equal(0, tensor.Strides[0]); Assert.Equal(6, tensor.Strides[1]); Assert.Equal(3, tensor.Strides[2]); - Assert.Equal(3, tensor.Strides[3]); - Assert.Equal(3, tensor.Strides[4]); + Assert.Equal(0, tensor.Strides[3]); + Assert.Equal(0, tensor.Strides[4]); Assert.Equal(1, tensor.Strides[5]); Assert.Equal(91, tensor[0, 0, 0, 0, 0, 0]); Assert.Equal(92, tensor[0, 0, 0, 0, 0, 1]); @@ -309,11 +347,11 @@ public static void TensorFactoryCreateUninitializedTests() Assert.Equal(1, t1.Lengths.Length); Assert.Equal(1, t1.Lengths[0]); Assert.Equal(1, t1.Strides.Length); - Assert.Equal(1, t1.Strides[0]); + Assert.Equal(0, t1.Strides[0]); Assert.False(t1.IsPinned); // Make sure can't index too many dimensions - Assert.Throws(() => + Assert.Throws(() => { var x = t1[1, 1]; }); @@ -367,7 +405,7 @@ public static void TensorFactoryCreateUninitializedTests() Assert.Equal(1, t1.Lengths.Length); Assert.Equal(1, t1.Lengths[0]); Assert.Equal(1, t1.Strides.Length); - Assert.Equal(1, t1.Strides[0]); + Assert.Equal(0, t1.Strides[0]); Assert.True(t1.IsPinned); // Make sure 2D array works with basic strides @@ -428,7 +466,7 @@ public static void TensorFactoryCreateUninitializedTests() Assert.Equal(t1[1, 0], t1[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => { + Assert.Throws(() => { var t1 = Tensor.CreateUninitialized([2, 2], [1, 1], false); }); } @@ -442,12 +480,12 @@ public static void TensorFactoryCreateTests() Assert.Equal(1, t1.Lengths.Length); Assert.Equal(1, t1.Lengths[0]); Assert.Equal(1, t1.Strides.Length); - Assert.Equal(1, t1.Strides[0]); + Assert.Equal(0, t1.Strides[0]); Assert.False(t1.IsPinned); Assert.Equal(0, t1[0]); // Make sure can't index too many dimensions - Assert.Throws(() => + Assert.Throws(() => { var x = t1[1, 1]; }); @@ -488,20 +526,18 @@ public static void TensorFactoryCreateTests() // Null should behave like empty array since there is no "null" span. t1 = Tensor.Create(null); - Assert.Equal(1, t1.Rank); - Assert.Equal(1, t1.Lengths.Length); - Assert.Equal(0, t1.Lengths[0]); - Assert.Equal(1, t1.Strides.Length); - Assert.Equal(0, t1.Strides[0]); + Assert.Equal(0, t1.Rank); + Assert.Equal(0, t1.Lengths.Length); + Assert.Equal(0, t1.Strides.Length); Assert.False(t1.IsPinned); // Make sure pinned works - t1 = Tensor.Create([1], true); + t1 = Tensor.Create([(nint)1], true); Assert.Equal(1, t1.Rank); Assert.Equal(1, t1.Lengths.Length); Assert.Equal(1, t1.Lengths[0]); Assert.Equal(1, t1.Strides.Length); - Assert.Equal(1, t1.Strides[0]); + Assert.Equal(0, t1.Strides[0]); Assert.True(t1.IsPinned); Assert.Equal(0, t1[0]); @@ -558,12 +594,10 @@ public static void TensorFactoryCreateTests() // Make sure lengths can't be negative Assert.Throws(() => { - Span a = [91, 92, -93, 94]; - var t1 = Tensor.Create([-1, 2], false); + var t1 = Tensor.Create([-1, (nint)2], false); }); Assert.Throws(() => { - Span a = [91, 92, -93, 94]; - var t1 = Tensor.Create([1, -2], false); + var t1 = Tensor.Create([(nint)1, -2], false); }); // Make sure 2D array works with strides to hit element 0,0,2,2 @@ -577,7 +611,7 @@ public static void TensorFactoryCreateTests() Assert.Equal(-93, t1[1, 1]); // Make sure you can't overlap elements using strides - Assert.Throws(() => { + Assert.Throws(() => { var t1 = Tensor.Create((Span)[2, 2], [1, 1], false); }); } @@ -588,20 +622,20 @@ public static void TensorCosineSimilarityTests() float[] a = [0, 0, 0, 1, 1, 1]; float[] b = [1, 0, 0, 1, 1, 0]; - Tensor left = Tensor.Create(a, [2,3]); - Tensor right = Tensor.Create(b, [2,3]); + Tensor left = Tensor.Create(a, lengths: [2,3]); + Tensor right = Tensor.Create(b, lengths: [2,3]); + + Tensor result = Tensor.Create(a, lengths: [2, 1]); + + result[0, 0] = Tensor.CosineSimilarity(left.AsReadOnlyTensorSpan([0..1, 0.. ]), right[0..1, 0..]); + result[1, 0] = Tensor.CosineSimilarity(left.AsReadOnlyTensorSpan([1.., 0..]), right[1.., 0..]); - Tensor result = Tensor.CosineSimilarity(left.AsReadOnlyTensorSpan(), right); Assert.Equal(2, result.Rank); Assert.Equal(2, result.Lengths[0]); - Assert.Equal(2, result.Lengths[1]); - + Assert.Equal(1, result.Lengths[1]); Assert.Equal(float.NaN, result[0, 0]); - Assert.Equal(float.NaN, result[0, 1]); - - Assert.Equal(0.57735, result[1, 0], .00001); - Assert.Equal(0.81649, result[1, 1], .00001); + Assert.Equal(0.81649, result[1, 0], .00001); } //[Fact] @@ -681,37 +715,38 @@ public static void TensorCosineSimilarityTests() [Fact] public static void TensorMultiplyTests() { - Tensor t0 = Tensor.Create(Enumerable.Range(0, 3), default); - Tensor t1 = Tensor.Create(Enumerable.Range(0, 3), [3, 1]); + Tensor t0 = Tensor.Create(Enumerable.Range(0, 3)); + Tensor t1 = Tensor.Create(Enumerable.Range(0, 3), lengths: [3, 1]); + + // TODO: This double broadcast will be added back in the future + //Tensor t2 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t1); + //Assert.Equal([3,3], t2.Lengths); + //Assert.Equal(0, t2[0, 0]); + //Assert.Equal(0, t2[0, 1]); + //Assert.Equal(0, t2[0, 2]); + //Assert.Equal(0, t2[1, 0]); + //Assert.Equal(1, t2[1, 1]); + //Assert.Equal(2, t2[1, 2]); + //Assert.Equal(0, t2[2, 0]); + //Assert.Equal(2, t2[2, 1]); + //Assert.Equal(4, t2[2, 2]); + + //t2 = Tensor.Multiply(t1.AsReadOnlyTensorSpan(), t0); + + //Assert.Equal([3, 3], t2.Lengths); + //Assert.Equal(0, t2[0, 0]); + //Assert.Equal(0, t2[0, 1]); + //Assert.Equal(0, t2[0, 2]); + //Assert.Equal(0, t2[1, 0]); + //Assert.Equal(1, t2[1, 1]); + //Assert.Equal(2, t2[1, 2]); + //Assert.Equal(0, t2[2, 0]); + //Assert.Equal(2, t2[2, 1]); + //Assert.Equal(4, t2[2, 2]); + + t1 = Tensor.Create(Enumerable.Range(0, 9), lengths: [3, 3]); Tensor t2 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t1); - Assert.Equal([3,3], t2.Lengths); - Assert.Equal(0, t2[0, 0]); - Assert.Equal(0, t2[0, 1]); - Assert.Equal(0, t2[0, 2]); - Assert.Equal(0, t2[1, 0]); - Assert.Equal(1, t2[1, 1]); - Assert.Equal(2, t2[1, 2]); - Assert.Equal(0, t2[2, 0]); - Assert.Equal(2, t2[2, 1]); - Assert.Equal(4, t2[2, 2]); - - t2 = Tensor.Multiply(t1.AsReadOnlyTensorSpan(), t0); - - Assert.Equal([3, 3], t2.Lengths); - Assert.Equal(0, t2[0, 0]); - Assert.Equal(0, t2[0, 1]); - Assert.Equal(0, t2[0, 2]); - Assert.Equal(0, t2[1, 0]); - Assert.Equal(1, t2[1, 1]); - Assert.Equal(2, t2[1, 2]); - Assert.Equal(0, t2[2, 0]); - Assert.Equal(2, t2[2, 1]); - Assert.Equal(4, t2[2, 2]); - - t1 = Tensor.Create(Enumerable.Range(0, 9), [3, 3]); - t2 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t1); - Assert.Equal([3, 3], t2.Lengths); Assert.Equal(0, t2[0, 0]); Assert.Equal(1, t2[0, 1]); @@ -727,7 +762,7 @@ public static void TensorMultiplyTests() [Fact] public static void TensorBroadcastTests() { - Tensor t0 = Tensor.Create(Enumerable.Range(0, 3), [1, 3, 1, 1, 1]); + Tensor t0 = Tensor.Create(Enumerable.Range(0, 3), lengths: [1, 3, 1, 1, 1]); Tensor t1 = Tensor.Broadcast(t0, [1, 3, 1, 2, 1]); Assert.Equal([1, 3, 1, 2, 1], t1.Lengths); @@ -749,8 +784,8 @@ public static void TensorBroadcastTests() Assert.Equal(2, t1[0, 2, 0, 0, 0]); Assert.Equal(2, t1[0, 2, 1, 0, 0]); - t0 = Tensor.Create(Enumerable.Range(0, 3), [1, 3]); - t1 = Tensor.Create(Enumerable.Range(0, 3), [3, 1]); + t0 = Tensor.Create(Enumerable.Range(0, 3), lengths: [1, 3]); + t1 = Tensor.Create(Enumerable.Range(0, 3), lengths: [3, 1]); var t2 = Tensor.Broadcast(t0, [3, 3]); Assert.Equal([3, 3], t2.Lengths); @@ -764,7 +799,7 @@ public static void TensorBroadcastTests() Assert.Equal(1, t2[2, 1]); Assert.Equal(2, t2[2, 2]); - t1 = Tensor.Create(Enumerable.Range(0, 3), [3, 1]); + t1 = Tensor.Create(Enumerable.Range(0, 3), lengths: [3, 1]); t2 = Tensor.Broadcast(t1, [3, 3]); Assert.Equal([3, 3], t2.Lengths); @@ -810,7 +845,7 @@ public static void TensorBroadcastTests() [Fact] public static void TensorResizeTests() { - Tensor t0 = Tensor.Create(Enumerable.Range(0, 8), [2, 2, 2]); + Tensor t0 = Tensor.Create(Enumerable.Range(0, 8), lengths: [2, 2, 2]); var t1 = Tensor.Resize(t0, [1]); Assert.Equal([1], t1.Lengths); Assert.Equal(0, t1[0]); @@ -858,7 +893,7 @@ public static void TensorResizeTests() [Fact] public static void TensorSplitTests() { - Tensor t0 = Tensor.Create(Enumerable.Range(0, 8), [2, 2, 2]); + Tensor t0 = Tensor.Create(Enumerable.Range(0, 8), lengths: [2, 2, 2]); var t1 = Tensor.Split(t0, 2, 0); Assert.Equal([1, 2, 2], t1[0].Lengths); Assert.Equal([1, 2, 2], t1[1].Lengths); @@ -899,7 +934,7 @@ public static void TensorSplitTests() [Fact] public static void TensorReverseTests() { - Tensor t0 = Tensor.Create(Enumerable.Range(0, 8), [2, 2, 2]); + Tensor t0 = Tensor.Create(Enumerable.Range(0, 8), lengths: [2, 2, 2]); var t1 = Tensor.Reverse(t0); Assert.Equal(7, t1[0, 0, 0]); Assert.Equal(6, t1[0, 0, 1]); @@ -944,9 +979,9 @@ public static void TensorReverseTests() [Fact] public static void TensorSetSliceTests() { - Tensor t0 = Tensor.Create(Enumerable.Range(0, 10), [2, 5]); - Tensor t1 = Tensor.Create(Enumerable.Range(10, 10), [2, 5]); - Tensor.SetSlice(t0, t1); + Tensor t0 = Tensor.Create(Enumerable.Range(0, 10), lengths: [2, 5]); + Tensor t1 = Tensor.Create(Enumerable.Range(10, 10), lengths: [2, 5]); + Tensor.SetSlice(t0, t1, .., ..); Assert.Equal(10, t0[0, 0]); Assert.Equal(11, t0[0, 1]); @@ -959,8 +994,8 @@ public static void TensorSetSliceTests() Assert.Equal(18, t0[1, 3]); Assert.Equal(19, t0[1, 4]); - t0 = Tensor.Create(Enumerable.Range(0, 10), [2, 5]); - t1 = Tensor.Create(Enumerable.Range(10, 5), [1, 5]); + t0 = Tensor.Create(Enumerable.Range(0, 10), lengths: [2, 5]); + t1 = Tensor.Create(Enumerable.Range(10, 5), lengths: [1, 5]); t0.SetSlice(t1, 0..1, ..); Assert.Equal(10, t0[0, 0]); @@ -974,8 +1009,8 @@ public static void TensorSetSliceTests() Assert.Equal(8, t0[1, 3]); Assert.Equal(9, t0[1, 4]); - t0 = Tensor.Create(Enumerable.Range(0, 10), [2, 5]); - t1 = Tensor.Create(Enumerable.Range(10, 5), [1, 5]); + t0 = Tensor.Create(Enumerable.Range(0, 10), lengths: [2, 5]); + t1 = Tensor.Create(Enumerable.Range(10, 5), lengths: [1, 5]); Tensor.SetSlice(t0, t1, 1..2, ..); Assert.Equal(0, t0[0, 0]); @@ -993,8 +1028,8 @@ public static void TensorSetSliceTests() [Fact] public static void TensorStackTests() { - Tensor t0 = Tensor.Create(Enumerable.Range(0, 10), [2, 5]); - Tensor t1 = Tensor.Create(Enumerable.Range(0, 10), [2, 5]); + Tensor t0 = Tensor.Create(Enumerable.Range(0, 10), lengths: [2, 5]); + Tensor t1 = Tensor.Create(Enumerable.Range(0, 10), lengths: [2, 5]); var resultTensor = Tensor.Stack([t0, t1]); Assert.Equal(3, resultTensor.Rank); @@ -1078,8 +1113,8 @@ public static void TensorStackTests() Assert.Equal(9, resultTensor[1, 4, 1]); // stacking 2x2 tensors along dimension 1 - Tensor v1 = Tensor.Create([1, 2, 3, 4], [2, 2]); - Tensor v2 = Tensor.Create([10, 20, 30, 40], [2, 2]); + Tensor v1 = Tensor.Create([1, 2, 3, 4], lengths: [2, 2]); + Tensor v2 = Tensor.Create([10, 20, 30, 40], lengths: [2, 2]); resultTensor = Tensor.StackAlongDimension(1, [v1, v2]); @@ -1098,9 +1133,7 @@ public static void TensorStackTests() Assert.Equal(30, resultTensor[1, 1, 0]); Assert.Equal(40, resultTensor[1, 1, 1]); - resultTensor = Tensor.StackAlongDimension(0, [v1, v2]); - - Tensor resultTensor2 = Tensor.Create([2, 2, 2]); + Tensor resultTensor2 = Tensor.Create([(nint)2, 2, 2]); Tensor.StackAlongDimension([v1, v2], resultTensor2, 1); Assert.Equal(3, resultTensor2.Rank); @@ -1122,7 +1155,7 @@ public static void TensorStackTests() [Fact] public static void TensorStdDevTests() { - Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), [2, 2]); + Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), lengths: [2, 2]); Assert.Equal(StdDev([0, 1, 2, 3]), Tensor.StdDev(t0), .1); @@ -1140,7 +1173,7 @@ public static void TensorStdDevTests() public static void TensorSumTests() { float[] values = new float[] { 1, 2, 3, 4, 5, 6 }; - Tensor t0 = Tensor.Create(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3]); + Tensor t0 = Tensor.Create(new float[] { 1, 2, 3, 4, 5, 6 }, lengths: [2, 3]); float sum = Tensor.Sum(t0); Assert.Equal(21, sum); @@ -1169,11 +1202,11 @@ public static void TensorSumTests() sum = Tensor.Sum(t1); Assert.Equal(9, sum); - Assert.Throws(()=> new Tensor(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], -1)); - Assert.Throws(()=> new Tensor(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], 100)); - Assert.Throws(()=> new Tensor(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], int.MinValue)); - Assert.Throws(()=> new Tensor(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], int.MaxValue)); - Assert.Throws(()=> new Tensor(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], 2)); + Assert.Throws(()=> Tensor.Create(new float[] { 1, 2, 3, 4, 5, 6 }, start: -1, lengths: [2, 3], strides: [])); + Assert.Throws(()=> Tensor.Create(new float[] { 1, 2, 3, 4, 5, 6 }, start: 100, lengths: [2, 3], strides: [])); + Assert.Throws(()=> Tensor.Create(new float[] { 1, 2, 3, 4, 5, 6 }, start: int.MinValue, lengths: [2, 3], strides: [])); + Assert.Throws(()=> Tensor.Create(new float[] { 1, 2, 3, 4, 5, 6 }, start: int.MaxValue, lengths: [2, 3], strides: [])); + Assert.Throws(()=> Tensor.Create(new float[] { 1, 2, 3, 4, 5, 6 }, start: 2, lengths: [2, 3], strides: [])); } public static float StdDev(float[] values) @@ -1190,7 +1223,7 @@ public static float StdDev(float[] values) [Fact] public static void TensorMeanTests() { - Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), [2, 2]); + Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), lengths: [2, 2]); Assert.Equal(Mean([0, 1, 2, 3]), Tensor.Average(t0), .1); } @@ -1208,8 +1241,8 @@ public static float Mean(float[] values) [Fact] public static void TensorConcatenateTests() { - Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), [2, 2]); - Tensor t1 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), [2, 2]); + Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), lengths: [2, 2]); + Tensor t1 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), lengths: [2, 2]); var resultTensor = Tensor.Concatenate([t0, t1]); Assert.Equal(2, resultTensor.Rank); @@ -1249,7 +1282,7 @@ public static void TensorConcatenateTests() Assert.Equal(2, resultTensor[6]); Assert.Equal(3, resultTensor[7]); - Tensor t2 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), [2, 2]); + Tensor t2 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), lengths: [2, 2]); resultTensor = Tensor.Concatenate([t0, t1, t2]); Assert.Equal(2, resultTensor.Rank); @@ -1303,9 +1336,9 @@ public static void TensorConcatenateTests() Assert.Equal(2, resultTensor[1, 4]); Assert.Equal(3, resultTensor[1, 5]); - t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [2, 3, 2]); - t1 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [2, 3, 2]); - t2 = Tensor.Create((Enumerable.Range(0, 8).Select(i => (float)i)), [2, 2, 2]); + t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [2, 3, 2]); + t1 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [2, 3, 2]); + t2 = Tensor.Create((Enumerable.Range(0, 8).Select(i => (float)i)), lengths: [2, 2, 2]); Assert.Throws(() => Tensor.ConcatenateOnDimension(0, [t0, t1, t2])); Assert.Throws(() => Tensor.ConcatenateOnDimension(2, [t0, t1, t2])); Assert.Throws(() => Tensor.ConcatenateOnDimension(5, [t0, t1, t2])); @@ -1330,9 +1363,9 @@ public static void TensorConcatenateTests() Helpers.AdjustIndices(resultTensor.Rank - 1, 1, ref indices, resultTensor.Lengths); } - t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [2, 2, 3]); - t1 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [2, 2, 3]); - t2 = Tensor.Create((Enumerable.Range(0, 8).Select(i => (float)i)), [2, 2, 2]); + t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [2, 2, 3]); + t1 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [2, 2, 3]); + t2 = Tensor.Create((Enumerable.Range(0, 8).Select(i => (float)i)), lengths: [2, 2, 2]); Assert.Throws(() => Tensor.Concatenate([t0, t1, t2])); Assert.Throws(() => Tensor.ConcatenateOnDimension(1, [t0, t1, t2])); resultTensor = Tensor.ConcatenateOnDimension(2, [t0, t1, t2]); @@ -1349,9 +1382,9 @@ public static void TensorConcatenateTests() Helpers.AdjustIndices(resultTensor.Rank - 1, 1, ref indices, resultTensor.Lengths); } - t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [3, 2, 2]); - t1 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [3, 2, 2]); - t2 = Tensor.Create((Enumerable.Range(0, 8).Select(i => (float)i)), [2, 2, 2]); + t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [3, 2, 2]); + t1 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [3, 2, 2]); + t2 = Tensor.Create((Enumerable.Range(0, 8).Select(i => (float)i)), lengths: [2, 2, 2]); Assert.Throws(() => Tensor.ConcatenateOnDimension(1, [t0, t1, t2])); Assert.Throws(() => Tensor.ConcatenateOnDimension(2, [t0, t1, t2])); resultTensor = Tensor.ConcatenateOnDimension(0, [t0, t1, t2]); @@ -1372,16 +1405,16 @@ public static void TensorConcatenateTests() [Fact] public static void TensorTransposeTests() { - Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), [2, 2]); - var t1 = Tensor.PermuteDimensions(t0); + Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), lengths: [2, 2]); + var t1 = t0.PermuteDimensions([]); Assert.Equal(0, t1[0, 0]); Assert.Equal(2, t1[0, 1]); Assert.Equal(1, t1[1, 0]); Assert.Equal(3, t1[1, 1]); - t0 = Tensor.Create((Enumerable.Range(0, 6).Select(i => (float)i)), [2, 3]); - t1 = Tensor.PermuteDimensions(t0); + t0 = Tensor.Create((Enumerable.Range(0, 6).Select(i => (float)i)), lengths: [2, 3]); + t1 = t0.PermuteDimensions([]); Assert.Equal(3, t1.Lengths[0]); Assert.Equal(2, t1.Lengths[1]); @@ -1392,8 +1425,8 @@ public static void TensorTransposeTests() Assert.Equal(2, t1[2, 0]); Assert.Equal(5, t1[2, 1]); - t0 = Tensor.Create((Enumerable.Range(0, 6).Select(i => (float)i)), [1, 2, 3]); - t1 = Tensor.PermuteDimensions(t0); + t0 = Tensor.Create((Enumerable.Range(0, 6).Select(i => (float)i)), lengths: [1, 2, 3]); + t1 = t0.PermuteDimensions([]); Assert.Equal(3, t1.Lengths[0]); Assert.Equal(2, t1.Lengths[1]); @@ -1405,8 +1438,8 @@ public static void TensorTransposeTests() Assert.Equal(2, t1[2, 0, 0]); Assert.Equal(5, t1[2, 1, 0]); - t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [2, 2, 3]); - t1 = Tensor.PermuteDimensions(t0); + t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [2, 2, 3]); + t1 = t0.PermuteDimensions([]); Assert.Equal(3, t1.Lengths[0]); Assert.Equal(2, t1.Lengths[1]); @@ -1424,8 +1457,8 @@ public static void TensorTransposeTests() Assert.Equal(5, t1[2, 1, 0]); Assert.Equal(11, t1[2, 1, 1]); - t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [2, 2, 3]); - t1 = Tensor.PermuteDimensions(t0, 1, 2, 0); + t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [2, 2, 3]); + t1 = t0.PermuteDimensions([1, 2, 0]); Assert.Equal(2, t1.Lengths[0]); Assert.Equal(3, t1.Lengths[1]); @@ -1447,7 +1480,7 @@ public static void TensorTransposeTests() [Fact] public static void TensorPermuteTests() { - Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), [2, 2]); + Tensor t0 = Tensor.Create((Enumerable.Range(0, 4).Select(i => (float)i)), lengths: [2, 2]); var t1 = Tensor.Transpose(t0); Assert.Equal(0, t1[0, 0]); @@ -1455,7 +1488,7 @@ public static void TensorPermuteTests() Assert.Equal(1, t1[1, 0]); Assert.Equal(3, t1[1, 1]); - t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), [2, 2, 3]); + t0 = Tensor.Create((Enumerable.Range(0, 12).Select(i => (float)i)), lengths: [2, 2, 3]); t1 = Tensor.Transpose(t0); Assert.Equal(2, t1.Lengths[0]); @@ -1479,7 +1512,7 @@ public static void TensorPermuteTests() public static void IntArrayAsTensor() { int[] a = [91, 92, -93, 94]; - TensorSpan t1 = a.AsTensorSpan(4); + TensorSpan t1 = a.AsTensorSpan(); nint[] dims = [4]; var tensor = Tensor.CreateUninitialized(dims.AsSpan(), false); t1.CopyTo(tensor); @@ -1508,7 +1541,7 @@ public static void IntArrayAsTensor() a[1] = 92; a[2] = -93; a[3] = 94; - t1 = a.AsTensorSpan(2, 2); + t1 = a.AsTensorSpan([2, 2]); dims = [2, 2]; tensor = Tensor.CreateUninitialized(dims.AsSpan(), false); t1.CopyTo(tensor); @@ -1613,7 +1646,7 @@ public static void TensorFillTest() public static void TensorClearTest() { int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - TensorSpan t1 = a.AsTensorSpan(3, 3); + TensorSpan t1 = a.AsTensorSpan([3, 3]); var tensor = Tensor.CreateUninitialized([3, 3], false); t1.CopyTo(tensor); var slice = tensor.Slice(0..2, 0..2); @@ -1643,7 +1676,7 @@ public static void TensorClearTest() } a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - t1 = a.AsTensorSpan(9); + t1 = a.AsTensorSpan([9]); tensor = Tensor.CreateUninitialized([9], false); t1.CopyTo(tensor); slice = tensor.Slice(0..1); @@ -1670,7 +1703,7 @@ public static void TensorClearTest() } a = [.. Enumerable.Range(0, 27)]; - t1 = a.AsTensorSpan(3, 3, 3); + t1 = a.AsTensorSpan([3, 3, 3]); tensor = Tensor.CreateUninitialized([3, 3, 3], false); t1.CopyTo(tensor); tensor.Clear(); @@ -1681,7 +1714,7 @@ public static void TensorClearTest() } a = [.. Enumerable.Range(0, 12)]; - t1 = a.AsTensorSpan(3, 2, 2); + t1 = a.AsTensorSpan([3, 2, 2]); tensor = Tensor.CreateUninitialized([3, 2, 2], false); t1.CopyTo(tensor); tensor.Clear(); @@ -1692,7 +1725,7 @@ public static void TensorClearTest() } a = [.. Enumerable.Range(0, 16)]; - t1 = a.AsTensorSpan(2, 2, 2, 2); + t1 = a.AsTensorSpan([2, 2, 2, 2]); tensor = Tensor.CreateUninitialized([2, 2, 2, 2], false); t1.CopyTo(tensor); tensor.Clear(); @@ -1703,7 +1736,7 @@ public static void TensorClearTest() } a = [.. Enumerable.Range(0, 24)]; - t1 = a.AsTensorSpan(3, 2, 2, 2); + t1 = a.AsTensorSpan([3, 2, 2, 2]); tensor = Tensor.CreateUninitialized([3, 2, 2, 2], false); t1.CopyTo(tensor); tensor.Clear(); @@ -1715,7 +1748,7 @@ public static void TensorClearTest() // Make sure clearing a slice of a SPan doesn't clear the whole thing. a = [.. Enumerable.Range(0, 9)]; - t1 = a.AsTensorSpan(3, 3); + t1 = a.AsTensorSpan([3, 3]); var spanSlice = t1.Slice(0..1, 0..3); spanSlice.Clear(); var spanEnumerator = spanSlice.GetEnumerator(); @@ -1736,7 +1769,7 @@ public static void TensorClearTest() // Make sure clearing a slice from the middle of a SPan doesn't clear the whole thing. a = [.. Enumerable.Range(0, 9)]; - t1 = a.AsTensorSpan(3, 3); + t1 = a.AsTensorSpan([3, 3]); spanSlice = t1.Slice(1..2, 0..3); spanSlice.Clear(); spanEnumerator = spanSlice.GetEnumerator(); @@ -1757,7 +1790,7 @@ public static void TensorClearTest() // Make sure clearing a slice from the end of a SPan doesn't clear the whole thing. a = [.. Enumerable.Range(0, 9)]; - t1 = a.AsTensorSpan(3, 3); + t1 = a.AsTensorSpan([3, 3]); spanSlice = t1.Slice(2..3, 0..3); spanSlice.Clear(); spanEnumerator = spanSlice.GetEnumerator(); @@ -1778,7 +1811,7 @@ public static void TensorClearTest() // Make sure it works with reference types. object[] o = [new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object()]; - TensorSpan spanObj = o.AsTensorSpan(3, 3); + TensorSpan spanObj = o.AsTensorSpan([3, 3]); spanObj.Clear(); var oSpanEnumerator = spanObj.GetEnumerator(); @@ -1789,7 +1822,7 @@ public static void TensorClearTest() // Make sure clearing a slice of a SPan with references it doesn't clear the whole thing. o = [new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object()]; - spanObj = o.AsTensorSpan(3, 3); + spanObj = o.AsTensorSpan([3, 3]); var oSpanSlice = spanObj.Slice(0..1, 0..3); oSpanSlice.Clear(); oSpanEnumerator = oSpanSlice.GetEnumerator(); @@ -1810,7 +1843,7 @@ public static void TensorClearTest() // Make sure clearing a slice of a SPan with references it doesn't clear the whole thing. o = [new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object()]; - spanObj = o.AsTensorSpan(3, 3); + spanObj = o.AsTensorSpan([3, 3]); oSpanSlice = spanObj.Slice(1..2, 0..3); oSpanSlice.Clear(); oSpanEnumerator = oSpanSlice.GetEnumerator(); @@ -1831,7 +1864,7 @@ public static void TensorClearTest() // Make sure clearing a slice of a SPan with references it doesn't clear the whole thing. o = [new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object(), new object()]; - spanObj = o.AsTensorSpan(3, 3); + spanObj = o.AsTensorSpan([3, 3]); oSpanSlice = spanObj.Slice(2..3, 0..3); oSpanSlice.Clear(); oSpanEnumerator = oSpanSlice.GetEnumerator(); @@ -1857,9 +1890,9 @@ public static void TensorCopyTest() int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; nint[] dims = [3, 3]; - TensorSpan leftSpan = leftData.AsTensorSpan(3, 3); + TensorSpan leftSpan = leftData.AsTensorSpan([3, 3]); var tensor = Tensor.CreateUninitialized(dims.AsSpan(), false); - TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); + TensorSpan rightSpan = rightData.AsTensorSpan([3, 3]); leftSpan.CopyTo(tensor); var leftEnum = leftSpan.GetEnumerator(); var tensorEnum = tensor.GetEnumerator(); @@ -1885,15 +1918,15 @@ public static void TensorCopyTest() { leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; dims = [15]; - TensorSpan leftSpan = leftData.AsTensorSpan(9); + TensorSpan leftSpan = leftData.AsTensorSpan([9]); tensor = Tensor.Create(dims.AsSpan(), false); leftSpan.CopyTo(tensor); } ); - Assert.Throws(() => + Assert.Throws(() => { - var l = leftData.AsTensorSpan(3, 3, 3); + var l = leftData.AsTensorSpan([3, 3, 3]); var r = new TensorSpan(); l.CopyTo(r); }); @@ -1904,10 +1937,10 @@ public static void TensorTryCopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; - TensorSpan leftSpan = leftData.AsTensorSpan(3, 3); + TensorSpan leftSpan = leftData.AsTensorSpan([3, 3]); nint[] dims = [3, 3]; var tensor = Tensor.CreateUninitialized(dims.AsSpan(), false); - TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); + TensorSpan rightSpan = rightData.AsTensorSpan([3, 3]); var success = leftSpan.TryCopyTo(tensor); Assert.True(success); success = tensor.TryCopyTo(rightSpan); @@ -1927,13 +1960,13 @@ public static void TensorTryCopyTest() leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; dims = [15]; - leftSpan = leftData.AsTensorSpan(9); + leftSpan = leftData.AsTensorSpan([9]); tensor = Tensor.Create(dims.AsSpan(), false); success = leftSpan.TryCopyTo(tensor); Assert.False(success); leftData = [.. Enumerable.Range(0, 27)]; - var l = leftData.AsTensorSpan(3, 3, 3); + var l = leftData.AsTensorSpan([3, 3, 3]); dims = [2, 2]; tensor = Tensor.Create(dims.AsSpan(), false); var r = new TensorSpan(); @@ -1955,7 +1988,7 @@ public static void TensorSliceTest() //Assert.Throws(() => tensor.Slice(0..1)); //Assert.Throws(() => tensor.Slice(1..2)); //Assert.Throws(() => tensor.Slice(0..1, 5..6)); - var intSpan = a.AsTensorSpan(3, 3); + var intSpan = a.AsTensorSpan([3, 3]); intSpan.CopyTo(tensor.AsTensorSpan()); var sp = tensor.Slice(1..3, 1..3); @@ -1992,7 +2025,7 @@ public static void TensorSliceTest() sp = tensor.Slice(0..1, 0..1); Assert.Equal(1, sp[0, 0]); - Assert.Throws(() => a.AsTensorSpan(3, 3).Slice(0..1, 0..1)[0, 1]); + Assert.Throws(() => a.AsTensorSpan([3, 3]).Slice(0..1, 0..1)[0, 1]); slice = [1]; Assert.Equal(slice, sp.ToArray()); enumerator = sp.GetEnumerator(); @@ -2017,7 +2050,7 @@ public static void TensorSliceTest() } int[] numbers = [.. Enumerable.Range(0, 27)]; - intSpan = numbers.AsTensorSpan(3, 3, 3); + intSpan = numbers.AsTensorSpan([3, 3, 3]); tensor = Tensor.CreateUninitialized([3, 3, 3], false); intSpan.CopyTo(tensor.AsTensorSpan()); sp = tensor.Slice(1..2, 1..2, 1..2); @@ -2050,7 +2083,7 @@ public static void TensorSliceTest() } numbers = [.. Enumerable.Range(0, 16)]; - intSpan = numbers.AsTensorSpan(2, 2, 2, 2); + intSpan = numbers.AsTensorSpan([2, 2, 2, 2]); tensor = Tensor.CreateUninitialized([2, 2, 2, 2], false); intSpan.CopyTo(tensor.AsTensorSpan()); sp = tensor.Slice(1..2, 0..2, 1..2, 0..2); @@ -2208,7 +2241,7 @@ public static void TensorUnsqueezeTest() Assert.Equal(0, tensor.Strides[0]); Assert.Equal(0, tensor.Strides[1]); - tensor = Tensor.Create([2], false); + tensor = Tensor.Create((ReadOnlySpan)[2], false); Assert.Equal(1, tensor.Rank); Assert.Equal(2, tensor.Lengths[0]); Assert.Equal(1, tensor.Strides[0]); @@ -2217,7 +2250,7 @@ public static void TensorUnsqueezeTest() Assert.Equal(2, tensor.Rank); Assert.Equal(1, tensor.Lengths[0]); Assert.Equal(2, tensor.Lengths[1]); - Assert.Equal(2, tensor.Strides[0]); + Assert.Equal(0, tensor.Strides[0]); Assert.Equal(1, tensor.Strides[1]); tensor = Tensor.Unsqueeze(tensor, 0); @@ -2225,8 +2258,8 @@ public static void TensorUnsqueezeTest() Assert.Equal(1, tensor.Lengths[0]); Assert.Equal(1, tensor.Lengths[1]); Assert.Equal(2, tensor.Lengths[2]); - Assert.Equal(2, tensor.Strides[0]); - Assert.Equal(2, tensor.Strides[1]); + Assert.Equal(0, tensor.Strides[0]); + Assert.Equal(0, tensor.Strides[1]); Assert.Equal(1, tensor.Strides[2]); tensor = Tensor.Unsqueeze(tensor, 0); @@ -2235,12 +2268,12 @@ public static void TensorUnsqueezeTest() Assert.Equal(1, tensor.Lengths[1]); Assert.Equal(1, tensor.Lengths[2]); Assert.Equal(2, tensor.Lengths[3]); - Assert.Equal(2, tensor.Strides[0]); - Assert.Equal(2, tensor.Strides[1]); - Assert.Equal(2, tensor.Strides[2]); + Assert.Equal(0, tensor.Strides[0]); + Assert.Equal(0, tensor.Strides[1]); + Assert.Equal(0, tensor.Strides[2]); Assert.Equal(1, tensor.Strides[3]); - tensor = Tensor.Create([2], false); + tensor = Tensor.Create((ReadOnlySpan)[2], false); Assert.Equal(1, tensor.Rank); Assert.Equal(2, tensor.Lengths[0]); @@ -2249,7 +2282,7 @@ public static void TensorUnsqueezeTest() Assert.Equal(2, tensor.Lengths[0]); Assert.Equal(1, tensor.Lengths[1]); - tensor = Tensor.Create([2], false); + tensor = Tensor.Create((ReadOnlySpan)[2], false); Assert.Equal(1, tensor.Rank); Assert.Equal(2, tensor.Lengths[0]); @@ -2265,15 +2298,15 @@ public static void TensorUnsqueezeTest() [Fact] public void TensorGreaterThanTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); Tensor result = Tensor.GreaterThan(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.Equal(new bool[] { false, false, true }, result.ToArray()); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.GreaterThan(tensor1.AsReadOnlyTensorSpan(), tensor2); @@ -2291,15 +2324,15 @@ public void TensorGreaterThanTest() [Fact] public void TensorGreaterThanOrEqualTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); Tensor result = Tensor.GreaterThanOrEqual(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.Equal(new bool[] { false, true, true }, result.ToArray()); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.GreaterThanOrEqual(tensor1.AsReadOnlyTensorSpan(), tensor2); @@ -2317,14 +2350,14 @@ public void TensorGreaterThanOrEqualTest() [Fact] public void TensorGreaterThanAllTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.GreaterThanAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.GreaterThanAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); @@ -2345,14 +2378,14 @@ public void TensorGreaterThanAllTest() [Fact] public void TensorGreaterThanOrEqualAllTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.GreaterThanOrEqualAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.GreaterThanOrEqualAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); @@ -2373,14 +2406,14 @@ public void TensorGreaterThanOrEqualAllTest() [Fact] public void TensorGreaterThanAnyTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.GreaterThanAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.GreaterThanAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); @@ -2404,14 +2437,14 @@ public void TensorGreaterThanAnyTest() [Fact] public void TensorGreaterThanOrEqualAnyTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.GreaterThanOrEqualAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.GreaterThanOrEqualAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); @@ -2435,15 +2468,15 @@ public void TensorGreaterThanOrEqualAnyTest() [Fact] public void TensorLessThanTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); Tensor result = Tensor.LessThan(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.Equal(new bool[] { true, false, false }, result.ToArray()); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.LessThan(tensor1.AsReadOnlyTensorSpan(), tensor2); @@ -2461,15 +2494,15 @@ public void TensorLessThanTest() [Fact] public void TensorLessThanOrEqualTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); Tensor result = Tensor.LessThanOrEqual(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.Equal(new bool[] { true, true, false }, result.ToArray()); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.LessThanOrEqual(tensor1.AsReadOnlyTensorSpan(), tensor2); @@ -2487,14 +2520,14 @@ public void TensorLessThanOrEqualTest() [Fact] public void TensorLessThanAllTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.LessThanAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.LessThanAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); @@ -2515,14 +2548,14 @@ public void TensorLessThanAllTest() [Fact] public void TensorLessThanAnyTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.LessThanAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.LessThanAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); @@ -2546,14 +2579,14 @@ public void TensorLessThanAnyTest() [Fact] public void TensorLessThanOrEqualAllTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.LessThanOrEqualAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.LessThanOrEqualAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); @@ -2577,14 +2610,14 @@ public void TensorLessThanOrEqualAllTest() [Fact] public void TensorLessThanOrEqualAnyTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3], false); - Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, [3], false); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 2, 2, 2 }, lengths: [3]); bool result = Tensor.LessThanOrEqualAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, [2, 2], false); - tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, [2, 2], false); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4 }, lengths: [2, 2]); + tensor2 = Tensor.Create(new int[] { 0, 2, 2, 3 }, lengths: [2, 2]); result = Tensor.LessThanOrEqualAny(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); @@ -2608,8 +2641,8 @@ public void TensorLessThanOrEqualAnyTest() [Fact] public void TensorEqualsTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3]); - Tensor tensor2 = Tensor.Create(new int[] { 1, 2, 3 }, [3]); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); Tensor result = Tensor.Equals(tensor1.AsReadOnlyTensorSpan(), tensor2); @@ -2627,8 +2660,8 @@ public void TensorEqualsTest() Assert.Equal(new bool[] { false, false, true }, result.ToArray()); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4, 5, 6 }, [2, 3]); - tensor2 = Tensor.Create(new int[] { 4, 5, 6 }, [3]); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4, 5, 6 }, lengths: [2, 3]); + tensor2 = Tensor.Create(new int[] { 4, 5, 6 }, lengths: [3]); result = Tensor.Equals(tensor1.AsReadOnlyTensorSpan(), tensor2); @@ -2638,8 +2671,8 @@ public void TensorEqualsTest() [Fact] public void TensorEqualsAllTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3]); - Tensor tensor2 = Tensor.Create(new int[] { 1, 2, 3 }, [3]); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor tensor2 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); bool result = Tensor.EqualsAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); @@ -2653,17 +2686,17 @@ public void TensorEqualsAllTest() result = Tensor.EqualsAll(tensor1.AsReadOnlyTensorSpan(), 3); Assert.False(result); - tensor1 = Tensor.Create(new int[] { 1, 1, 1 }, [3]); + tensor1 = Tensor.Create(new int[] { 1, 1, 1 }, lengths: [3]); result = Tensor.EqualsAll(tensor1.AsReadOnlyTensorSpan(), 1); Assert.True(result); - tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4, 5, 6 }, [2, 3]); - tensor2 = Tensor.Create(new int[] { 4, 5, 6 }, [3]); + tensor1 = Tensor.Create(new int[] { 1, 2, 3, 4, 5, 6 }, lengths: [2, 3]); + tensor2 = Tensor.Create(new int[] { 4, 5, 6 }, lengths: [3]); result = Tensor.EqualsAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.False(result); - tensor1 = Tensor.Create(new int[] { 4, 5, 6, 4, 5, 6 }, [2, 3]); + tensor1 = Tensor.Create(new int[] { 4, 5, 6, 4, 5, 6 }, lengths: [2, 3]); result = Tensor.EqualsAll(tensor1.AsReadOnlyTensorSpan(), tensor2); Assert.True(result); } @@ -2671,9 +2704,9 @@ public void TensorEqualsAllTest() [Fact] public void TensorFilteredUpdateTest() { - Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, [3]); - Tensor filter = Tensor.Create(new bool[] { true, false, false }, [3]); - Tensor replace = Tensor.Create(new int[] { -1, -1, -1 }, [3]); + Tensor tensor1 = Tensor.Create(new int[] { 1, 2, 3 }, lengths: [3]); + Tensor filter = Tensor.Create(new bool[] { true, false, false }, lengths: [3]); + Tensor replace = Tensor.Create(new int[] { -1, -1, -1 }, lengths: [3]); Tensor.FilteredUpdate(tensor1.AsTensorSpan(), filter, 2); Assert.Equal(new int[] { 2, 2, 3 }, tensor1.ToArray()); @@ -2681,7 +2714,7 @@ public void TensorFilteredUpdateTest() Tensor.FilteredUpdate(tensor1.AsTensorSpan(), filter, replace); Assert.Equal(new int[] { -1, 2, 3 }, tensor1.ToArray()); - filter = Tensor.Create(new bool[] { true, true, true}, [3]); + filter = Tensor.Create(new bool[] { true, true, true}, lengths: [3]); Tensor.FilteredUpdate(tensor1.AsTensorSpan(), filter, replace); Assert.Equal(new int[] { -1, -1, -1 }, tensor1.ToArray()); } @@ -2689,7 +2722,7 @@ public void TensorFilteredUpdateTest() [Fact] public void TensorObjectFillTests() { - ITensor tensor = (ITensor)new Tensor(new int[4], new nint[] { 2, 2 }, 0); + ITensor tensor = (ITensor)new Tensor(new int[4], new nint[] { 2, 2 }); tensor.Fill(5); Assert.Equal(5, tensor[0, 0]); @@ -2710,7 +2743,7 @@ public void TensorObjectFillTests() [Fact] public void TensorObjectIndexerTests() { - ITensor tensor = new Tensor(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0); + ITensor tensor = new Tensor(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }); Assert.Equal(1, tensor[new nint[] { 0, 0 }]); Assert.Equal(2, tensor[new nint[] { 0, 1 }]); @@ -2741,7 +2774,7 @@ public void TensorObjectIndexerTests() [Fact] public void TensorGetPinnedHandleTests() { - Tensor tensor = new Tensor(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0); + Tensor tensor = new Tensor(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }); using MemoryHandle handle = tensor.GetPinnedHandle(); unsafe From d154e4793a80515bb4e77c35d08a7cf13ff13977 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:44:15 -0700 Subject: [PATCH 5/5] [release/10.0-preview4] [Test Only] Fix Idn failing tests (#115021) * Fix Idn failing tests * Minor fix --------- Co-authored-by: Tarek Mahmoud Sayed Co-authored-by: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com> --- .../IdnMapping/Data/Factory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/IdnMapping/Data/Factory.cs b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/IdnMapping/Data/Factory.cs index c2610eb092ced3..f1d96fc1c94097 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/IdnMapping/Data/Factory.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/IdnMapping/Data/Factory.cs @@ -28,7 +28,7 @@ private static Stream GetIdnaTestTxt() string fileName = null; if (PlatformDetection.ICUVersion >= new Version(76, 0)) fileName = "IdnaTest_16.txt"; - else if (PlatformDetection.ICUVersion >= new Version(74, 0)) + else if (PlatformDetection.ICUVersion >= new Version(72, 0)) fileName = "IdnaTest_15_1.txt"; else if (PlatformDetection.ICUVersion >= new Version(66, 0) || PlatformDetection.IsHybridGlobalizationOnApplePlatform) fileName = "IdnaTest_13.txt"; @@ -67,7 +67,7 @@ private static IConformanceIdnaTest GetConformanceIdnaTest(string line, int line { if (PlatformDetection.ICUVersion >= new Version(76, 0)) return new Unicode_16_0_IdnaTest(line, lineCount); - else if (PlatformDetection.ICUVersion >= new Version(74, 0)) + else if (PlatformDetection.ICUVersion >= new Version(72, 0)) return new Unicode_15_1_IdnaTest(line, lineCount); else if (PlatformDetection.ICUVersion >= new Version(66, 0) || PlatformDetection.IsHybridGlobalizationOnApplePlatform) return new Unicode_13_0_IdnaTest(line, lineCount);