File: System\Numerics\Tensors\netcore\Tensor.cs
Web Access
Project: src\src\libraries\System.Numerics.Tensors\src\System.Numerics.Tensors.csproj (System.Numerics.Tensors)
// 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.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
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
{
    /// <summary>
    /// Represents a tensor.
    /// </summary>
    [Experimental(Experimentals.TensorTDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
    public sealed class Tensor<T>
        : ITensor<Tensor<T>, T>
    {
        /// <summary>A byref or a native ptr.</summary>
        internal readonly T[] _values;
        /// <summary>The number of elements this Tensor contains.</summary>
        internal readonly nint _flattenedLength;
        /// <summary>The lengths of each dimension.</summary>
        internal readonly nint[] _lengths;
        /// <summary>The strides representing the memory offsets for each dimension.</summary>
        internal readonly nint[] _strides;
        /// <summary>If the backing memory is permanently pinned (so not just using a fixed statement).</summary>
        internal readonly bool _isPinned;
 
        /// <summary>
        /// Creates a new empty Tensor.
        /// </summary>
        internal Tensor()
        {
            _flattenedLength = 0;
            _values = [];
            _lengths = [];
            _strides = [];
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, bool isPinned = false) : this(values, lengths, Array.Empty<nint>(), isPinned) { }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, ReadOnlySpan<nint> strides, bool isPinned = false)
        {
            if (values == null)
            {
                if (_flattenedLength != 0)
                    ThrowHelper.ThrowArgumentOutOfRangeException();
                _flattenedLength = 0;
                _values = [];
                _lengths = [];
                _strides = [];
                return; // returns default
            }
 
            _lengths = lengths.IsEmpty ? [values.Length] : lengths.ToArray();
 
            _flattenedLength = TensorSpanHelpers.CalculateTotalLength(_lengths);
            _strides = strides.IsEmpty ? TensorSpanHelpers.CalculateStrides(_lengths, _flattenedLength) : strides.ToArray();
            TensorSpanHelpers.ValidateStrides(_strides, _lengths);
            nint maxElements = TensorSpanHelpers.ComputeMaxLinearIndex(_strides, _lengths);
 
            if (Environment.Is64BitProcess)
            {
                // See comment in Span<T>.Slice for how this works.
                if ((ulong)(uint)maxElements >= (ulong)(uint)values.Length && values.Length != 0)
                    ThrowHelper.ThrowArgument_InvalidStridesAndLengths();
            }
            else
            {
                if (((uint)maxElements >= (uint)(values.Length)) && values.Length != 0)
                    ThrowHelper.ThrowArgument_InvalidStridesAndLengths();
            }
 
            _values = values;
            _isPinned = isPinned;
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with the default value of T. If <paramref name="pinned"/> is true, the memory will be pinned.
        /// </summary>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        /// <param name="pinned">A <see cref="bool"/> whether the underlying data should be pinned or not.</param>
        static Tensor<T> ITensor<Tensor<T>, T>.Create(ReadOnlySpan<nint> lengths, bool pinned)
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
            return new Tensor<T>(values, lengths.ToArray(), pinned);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with the default value of T. If <paramref name="pinned"/> is true, the memory will be pinned.
        /// </summary>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        /// <param name="strides">A <see cref="ReadOnlySpan{T}"/> indicating the strides of each dimension.</param>
        /// <param name="pinned">A <see cref="bool"/> whether the underlying data should be pinned or not.</param>
        static Tensor<T> ITensor<Tensor<T>, T>.Create(ReadOnlySpan<nint> lengths, ReadOnlySpan<nint> strides, bool pinned)
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
            return new Tensor<T>(values, lengths.ToArray(), strides.ToArray(), pinned);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and does not initialize it. If <paramref name="pinned"/> is true, the memory will be pinned.
        /// </summary>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        /// <param name="pinned">A <see cref="bool"/> whether the underlying data should be pinned or not.</param>
        static Tensor<T> ITensor<Tensor<T>, T>.CreateUninitialized(ReadOnlySpan<nint> lengths, bool pinned)
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = GC.AllocateUninitializedArray<T>((int)linearLength, pinned);
            return new Tensor<T>(values, lengths.ToArray(), pinned);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and does not initialize it. If <paramref name="pinned"/> is true, the memory will be pinned.
        /// </summary>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        /// <param name="strides">A <see cref="ReadOnlySpan{T}"/> indicating the strides of each dimension.</param>
        /// <param name="pinned">A <see cref="bool"/> whether the underlying data should be pinned or not.</param>
        static Tensor<T> ITensor<Tensor<T>, T>.CreateUninitialized(ReadOnlySpan<nint> lengths, ReadOnlySpan<nint> strides, bool pinned)
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = GC.AllocateUninitializedArray<T>((int)linearLength, pinned);
            return new Tensor<T>(values, lengths.ToArray(), strides.ToArray(), pinned);
        }
 
        // ITensor
        /// <summary>
        /// The Empty Tensor.
        /// </summary>
        public static Tensor<T> Empty { get; } = new();
 
        /// <summary>
        /// Gets a value indicating whether this <see cref="Tensor{T}"/> is empty.
        /// </summary>
        /// <value><see langword="true"/> if this tensor is empty; otherwise, <see langword="false"/>.</value>
        public bool IsEmpty => _lengths.Length == 0;
 
        /// <summary>
        /// Gets a value indicating whether the backing memory of the <see cref="Tensor{T}"/> is pinned."/>
        /// </summary>
        /// <value><see langword="true"/> if the backing memory is pinned; otherwise, <see langword="false"/>.</value>
        public bool IsPinned => _isPinned;
 
        /// <summary>
        /// Gets a value indicating the rank, or number of dimensions, of this <see cref="Tensor{T}"/>.
        /// </summary>
        /// <value><see cref="nint"/> with the number of dimensions.</value>
        public int Rank => _lengths.Length;
 
        /// <summary>
        /// The number of items in the <see cref="Tensor{T}"/>.
        /// </summary>
        /// <value><see cref="nint"/> with the number of items.</value>
        public nint FlattenedLength => _flattenedLength;
 
        /// <summary>
        /// Gets the length of each dimension in this <see cref="Tensor{T}"/>.
        /// </summary>
        /// <value><see cref="ReadOnlySpan{T}"/> with the lengths of each dimension.</value>
        public ReadOnlySpan<nint> Lengths => _lengths;
 
        /// <summary>
        /// Gets the length of each dimension in this <see cref="Tensor{T}"/>.
        /// </summary>
        /// <value><see cref="ReadOnlySpan{T}"/> with the lengths of each dimension.</value>
        ReadOnlySpan<nint> IReadOnlyTensor<Tensor<T>, T>.Lengths => _lengths;
 
 
        /// <summary>
        /// Gets the strides of each dimension in this <see cref="Tensor{T}"/>.
        /// </summary>
        /// <value><see cref="ReadOnlySpan{T}"/> with the strides of each dimension.</value>
        public ReadOnlySpan<nint> Strides => _strides;
 
        /// <summary>
        /// Gets the strides of each dimension in this <see cref="Tensor{T}"/>.
        /// </summary>
        /// <value><see cref="ReadOnlySpan{T}"/> with the strides of each dimension.</value>
        ReadOnlySpan<nint> IReadOnlyTensor<Tensor<T>, T>.Strides => _strides;
 
        bool ITensor<Tensor<T>, T>.IsReadOnly => false;
 
        /// <summary>
        /// Returns a reference to specified element of the Tensor.
        /// </summary>
        /// <param name="indexes"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when index less than 0 or index greater than or equal to FlattenedLength
        /// </exception>
        public ref T this[params scoped ReadOnlySpan<nint> indexes] => ref AsTensorSpan()[indexes];
 
        /// <summary>
        /// Returns a reference to specified element of the Tensor.
        /// </summary>
        /// <param name="indexes"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when index less than 0 or index greater than or equal to FlattenedLength
        /// </exception>
        public ref T this[params scoped ReadOnlySpan<NIndex> indexes] => ref AsTensorSpan()[indexes];
 
        /// <summary>
        /// Returns a slice of the Tensor.
        /// </summary>
        /// <param name="ranges"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when any index is less than 0 or any index is greater than or equal to FlattenedLength
        /// </exception>
        public Tensor<T> this[params ReadOnlySpan<NRange> ranges]
        {
            get
            {
                if (ranges.Length != Rank)
                    ThrowHelper.ThrowIndexOutOfRangeException();
 
                return Slice(ranges);
            }
            set
            {
                TensorSpan<T> srcSpan;
                if (ranges == ReadOnlySpan<NRange>.Empty)
                {
                    if (!Lengths.SequenceEqual(value.Lengths))
                        ThrowHelper.ThrowArgument_SetSliceNoRange(nameof(value));
                    srcSpan = AsTensorSpan().Slice(Lengths);
                }
                else
                    srcSpan = AsTensorSpan().Slice(ranges);
 
                if (!srcSpan.Lengths.SequenceEqual(value.Lengths))
                    ThrowHelper.ThrowArgument_SetSliceInvalidShapes(nameof(value));
 
                value.AsTensorSpan().CopyTo(srcSpan);
            }
        }
 
        /// <summary>
        /// Returns the specified element of the Tensor.
        /// </summary>
        /// <param name="indexes"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when index less than 0 or index greater than or equal to FlattenedLength
        /// </exception>
        T ITensor<Tensor<T>, T>.this[params ReadOnlySpan<nint> indexes]
        {
            get
            {
                return this[indexes];
            }
            set
            {
                this[indexes] = value;
            }
        }
 
        /// <summary>
        /// Returns the specified element of the Tensor.
        /// </summary>
        /// <param name="indexes"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when index less than 0 or index greater than or equal to FlattenedLength
        /// </exception>
        T ITensor<Tensor<T>, T>.this[params ReadOnlySpan<NIndex> indexes]
        {
            get
            {
                return this[indexes];
            }
            set
            {
                this[indexes] = value;
            }
        }
 
        /// <summary>
        /// Returns the specified element of the ReadOnlyTensor.
        /// </summary>
        /// <param name="indexes"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when index less than 0 or index greater than or equal to FlattenedLength
        /// </exception>
        T IReadOnlyTensor<Tensor<T>, T>.this[params ReadOnlySpan<nint> indexes] => AsReadOnlyTensorSpan()[indexes];
 
        /// <summary>
        /// Returns the specified element of the ReadOnlyTensor.
        /// </summary>
        /// <param name="indexes"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when index less than 0 or index greater than or equal to FlattenedLength
        /// </exception>
        T IReadOnlyTensor<Tensor<T>, T>.this[params ReadOnlySpan<NIndex> indexes] => AsReadOnlyTensorSpan()[indexes];
 
        /// <summary>
        /// Returns a slice of the ReadOnlyTensor.
        /// </summary>
        /// <param name="ranges"></param>
        /// <returns></returns>
        /// <exception cref="IndexOutOfRangeException">
        /// Thrown when any index is less than 0 or any index is greater than or equal to FlattenedLength
        /// </exception>
        Tensor<T> IReadOnlyTensor<Tensor<T>, T>.this[params ReadOnlySpan<NRange> ranges]
        {
            get
            {
                if (ranges.Length != Rank)
                    ThrowHelper.ThrowIndexOutOfRangeException();
 
                return Slice(ranges);
            }
        }
 
        // REVIEW: WE WILL WANT THIS CHANGED FROM A BOOL TO SOME FILTER EXPRESSION.
        /// <summary>
        ///
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public Tensor<T> this[Tensor<bool> filter]
        {
            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<T> srcSpan = _values;
                Span<bool> filterSpan = filter._values;
 
                nint linearLength = TensorHelpers.CountTrueElements(filter);
 
                T[] values = _isPinned ? GC.AllocateArray<T>((int)linearLength, _isPinned) : (new T[linearLength]);
                int index = 0;
                for (int i = 0; i < filterSpan.Length; i++)
                {
                    if (filterSpan[i])
                    {
                        values[i] = srcSpan[index++];
                    }
                }
 
                return new Tensor<T>(values, [linearLength], _isPinned);
            }
        }
 
        /// <summary>
        /// Defines an implicit conversion of an array to a <see cref="Tensor{T}"/>.
        /// </summary>
        public static implicit operator Tensor<T>(T[] array) => new Tensor<T>(array, [array.Length]);
 
        /// <summary>
        /// Defines an implicit conversion of a <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/>.
        /// </summary>
        public static implicit operator TensorSpan<T>(Tensor<T> value) => new TensorSpan<T>(ref MemoryMarshal.GetArrayDataReference(value._values), value._lengths, value._strides, value._flattenedLength);
 
        /// <summary>
        /// Defines an implicit conversion of a <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/>.
        /// </summary>
        public static implicit operator ReadOnlyTensorSpan<T>(Tensor<T> value) => new ReadOnlyTensorSpan<T>(ref MemoryMarshal.GetArrayDataReference(value._values), value._lengths, value._strides, value.FlattenedLength);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/> pointing to the same backing memory."/>
        /// </summary>
        /// <returns><see cref="TensorSpan{T}"/></returns>
        public TensorSpan<T> AsTensorSpan() => new TensorSpan<T>(ref MemoryMarshal.GetArrayDataReference(_values), _lengths, _strides, _flattenedLength);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/> pointing to the same backing memory based on the provided ranges."/>
        /// </summary>
        /// <param name="start">The ranges you want in the <see cref="TensorSpan{T}"/>.</param>
        /// <returns><see cref="TensorSpan{T}"/> based on the provided ranges.</returns>
        public TensorSpan<T> AsTensorSpan(params scoped ReadOnlySpan<NRange> start) => AsTensorSpan().Slice(start);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/> pointing to the same backing memory based on the provided start locations."/>
        /// </summary>
        /// <param name="start">The start location you want in the <see cref="TensorSpan{T}"/>.</param>
        /// <returns><see cref="TensorSpan{T}"/> based on the provided ranges.</returns>
        public TensorSpan<T> AsTensorSpan(params scoped ReadOnlySpan<nint> start) => Slice(start);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/> pointing to the same backing memory based on the provided start indexes."/>
        /// </summary>
        /// <param name="startIndex">The ranges you want in the <see cref="TensorSpan{T}"/>.</param>
        /// <returns><see cref="TensorSpan{T}"/> based on the provided ranges.</returns>
        public TensorSpan<T> AsTensorSpan(params scoped ReadOnlySpan<NIndex> startIndex) => AsTensorSpan().Slice(startIndex);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="ReadOnlyTensorSpan{T}"/> pointing to the same backing memory."/>
        /// </summary>
        /// <returns><see cref="ReadOnlyTensorSpan{T}"/></returns>
        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan() => new ReadOnlyTensorSpan<T>(ref MemoryMarshal.GetArrayDataReference(_values), _lengths, _strides, _flattenedLength);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="ReadOnlyTensorSpan{T}"/> pointing to the same backing memory based on the provided ranges."/>
        /// </summary>
        /// <param name="start">The ranges you want in the <see cref="ReadOnlyTensorSpan{T}"/></param>
        /// <returns></returns>
        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan(params scoped ReadOnlySpan<NRange> start) => AsTensorSpan().Slice(start);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="ReadOnlyTensorSpan{T}"/> pointing to the same backing memory based on the provided start locations."/>
        /// </summary>
        /// <param name="start">The start locations you want in the <see cref="ReadOnlyTensorSpan{T}"/></param>
        /// <returns></returns>
        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan(params scoped ReadOnlySpan<nint> start) => Slice(start);
 
        /// <summary>
        /// Converts this <see cref="Tensor{T}"/> to a <see cref="ReadOnlyTensorSpan{T}"/> pointing to the same backing memory based on the provided start indexes."/>
        /// </summary>
        /// <param name="startIndex">The start indexes you want in the <see cref="ReadOnlyTensorSpan{T}"/></param>
        /// <returns></returns>
        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan(params scoped ReadOnlySpan<NIndex> startIndex) => AsTensorSpan().Slice(startIndex);
 
        /// <summary>
        /// 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.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public ref T GetPinnableReference() => ref AsTensorSpan().GetPinnableReference();
 
        /// <summary>
        /// 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.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        ref readonly T IReadOnlyTensor<Tensor<T>, T>.GetPinnableReference() => ref AsReadOnlyTensorSpan().GetPinnableReference();
 
        /// <summary>
        /// Forms a slice out of the given tensor
        /// </summary>
        /// <param name="start">The ranges for the slice</param>
        /// <returns><see cref="Tensor{T}"/> as a copy of the provided ranges.</returns>
        // REVIEW: CURRENTLY DOES A COPY.
        public Tensor<T> Slice(params ReadOnlySpan<NRange> start)
        {
            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");
 
            TensorSpan<T> s = AsTensorSpan(start);
            T[] values = _isPinned ? GC.AllocateArray<T>(checked((int)s.FlattenedLength), _isPinned) : (new T[s.FlattenedLength]);
            var outTensor = new Tensor<T>(values, s.Lengths.ToArray(), _isPinned);
            s.CopyTo(outTensor);
            return outTensor;
        }
 
        /// <summary>
        /// Forms a slice out of the given tensor
        /// </summary>
        /// <param name="start">The start indexes for the slice</param>
        /// <returns><see cref="Tensor{T}"/> as a copy of the provided ranges.</returns>
        // REVIEW: CURRENTLY DOES A COPY.
        public Tensor<T> Slice(params ReadOnlySpan<nint> start)
        {
            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);
        }
 
        /// <summary>
        /// Forms a slice out of the given tensor
        /// </summary>
        /// <param name="startIndex">The start indexes for the slice</param>
        /// <returns><see cref="Tensor{T}"/> as a copy of the provided ranges.</returns>
        // REVIEW: CURRENTLY DOES A COPY.
        public Tensor<T> Slice(params ReadOnlySpan<NIndex> startIndex)
        {
            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);
        }
 
        /// <summary>
        /// Clears the contents of this tensor.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe void Clear() => AsTensorSpan().Clear();
 
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="destination">The span to copy items into.</param>
        /// <exception cref="ArgumentException">
        /// Thrown when the destination TensorSpan is shorter than the source Tensor.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void CopyTo(TensorSpan<T> destination) => AsTensorSpan().CopyTo(destination);
 
        /// <summary>
        /// Fills the contents of this span with the given value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Fill(T value) => AsTensorSpan().Fill(value);
 
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="destination">The span to copy items into.</param>
        /// <returns>If the destination span is shorter than the source tensor, this method
        /// return false and no data is written to the destination.</returns>
        public bool TryCopyTo(TensorSpan<T> destination) => AsTensorSpan().TryCopyTo(destination);
 
        /// <summary>
        /// Flattens the contents of this Tensor into the provided <see cref="Span{T}"/>.
        /// </summary>
        /// <param name="destination">The span to copy items into.</param>
        public void FlattenTo(Span<T> destination) => AsTensorSpan().FlattenTo(destination);
 
        /// <summary>
        /// Flattens the contents of this Tensor into the provided <see cref="Span{T}"/>.
        /// </summary>
        /// <param name="destination">The span to copy items into.</param>
        public bool TryFlattenTo(Span<T> destination) => AsTensorSpan().TryFlattenTo(destination);
 
        // IEnumerable
        /// <summary>
        /// Gets an <see cref="IEnumerator{T}"/> for the <see cref="Tensor{T}"/>.
        /// </summary>
        /// <returns><see cref="IEnumerator{T}"/></returns>
        IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
 
        // IEnumerable
        /// <summary>
        /// Gets an <see cref="IEnumerator{T}"/> for the <see cref="Tensor{T}"/>.
        /// </summary>
        /// <returns><see cref="IEnumerator{T}"/></returns>
        public IEnumerator<T> GetEnumerator() => new Enumerator(this);
 
        /// <summary>
        /// Gets an <see cref="IEnumerator"/> for the <see cref="Tensor{T}"/>."/>
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
        private struct Enumerator : IEnumerator<T>
        {
            /// <summary>The span being enumerated.</summary>
            private readonly Tensor<T> _tensor;
            /// <summary>
            ///
            /// </summary>
            private nint[] _curIndices;
            /// <summary>The total item count.</summary>
            private nint _items;
 
            /// <summary>Initialize the enumerator.</summary>
            /// <param name="tensor">The tensor to enumerate.</param>
            internal Enumerator(Tensor<T> tensor)
            {
                _tensor = tensor;
                _items = -1;
                _curIndices = new nint[_tensor.Rank];
                _curIndices[_tensor.Rank - 1] = -1;
            }
 
            /// <summary>Advances the enumerator to the next element of the span.</summary>
            public bool MoveNext()
            {
                TensorSpanHelpers.AdjustIndexes(_tensor.Rank - 1, 1, ref _curIndices, _tensor.Lengths);
 
                _items++;
                return _items < _tensor.FlattenedLength;
            }
 
            /// <summary>
            /// Resets the enumerator to the beginning of the span.
            /// </summary>
            public void Reset()
            {
                Array.Clear(_curIndices);
                _curIndices[_tensor.Rank - 1] = -1;
            }
 
            /// <summary>
            ///
            /// </summary>
            public void Dispose()
            {
 
            }
 
            /// <summary>
            /// Current T value of the <see cref="IEnumerator{T}"/>
            /// </summary>
            T IEnumerator<T>.Current => _tensor[_curIndices];
 
            /// <summary>
            /// Current <see cref="object"/> of the <see cref="IEnumerator"/>
            /// </summary>
            object? IEnumerator.Current => _tensor[_curIndices];
        }
 
        // REVIEW: PENDING API REVIEW TO DETERMINE IMPLEMENTATION
        /// <summary>
        /// Gets the hash code for the <see cref="Tensor{T}"/>.
        /// </summary>
        /// <returns>The hash code of the tensor.</returns>
        /// <exception cref="NotImplementedException">In all cases.</exception>
        public override int GetHashCode()
        {
            throw new NotImplementedException();
        }
 
        /// <summary>
        /// Get a string representation of the tensor.
        /// </summary>
        private string ToMetadataString()
        {
            var sb = new StringBuilder("[");
 
            int n = Rank;
            if (n == 0)
            {
                sb.Append(']');
            }
            else
            {
                for (int i = 0; i < n; i++)
                {
                    sb.Append(Lengths[i]);
                    if (i + 1 < n)
                        sb.Append('x');
                }
 
                sb.Append(']');
            }
            sb.Append($", type = {typeof(T)}, isPinned = {IsPinned}");
 
            return sb.ToString();
        }
 
        /// <summary>
        /// Creates a <see cref="string"/> representation of the <see cref="TensorSpan{T}"/>."/>
        /// </summary>
        /// <param name="maximumLengths">Maximum Length of each dimension</param>
        /// <returns>A <see cref="string"/> representation of the <see cref="Tensor{T}"/></returns>
        public string ToString(params ReadOnlySpan<nint> maximumLengths)
        {
            if (maximumLengths.Length == 0)
                maximumLengths = (from number in Enumerable.Range(0, Rank) select (nint)5).ToArray();
            var sb = new StringBuilder();
            sb.AppendLine(ToMetadataString());
            sb.AppendLine("{");
            sb.Append(AsTensorSpan().ToString(maximumLengths));
            sb.AppendLine("}");
            return sb.ToString();
        }
    }
}