File: System\Numerics\Tensors\netcore\Tensor.Factory.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.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
{
    /// <summary>
    /// Provides methods for creating tensors.
    /// </summary>
    public static partial class Tensor
    {
        /// <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>
        public static Tensor<T> Create<T>(scoped ReadOnlySpan<nint> lengths, bool pinned = false)
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
            return Create(values, lengths, [], 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>
        public static Tensor<T> Create<T>(scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false)
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
            return Create(values, lengths, strides, pinned);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> from the provided <paramref name="values"/>. If the product of the
        /// <paramref name="lengths"/> does not equal the length of the <paramref name="values"/> array, an exception will be thrown.
        /// </summary>
        /// <param name="values">An array of the backing memory.</param>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        /// <param name="pinned">A <see cref="bool"/> indicating whether the <paramref name="values"/> were pinned or not.</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static Tensor<T> Create<T>(T[] values, scoped ReadOnlySpan<nint> lengths, bool pinned = false)
            => Create(values, lengths, [], pinned);
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> from the provided <paramref name="values"/>. If the product of the
        /// <paramref name="lengths"/> does not equal the length of the <paramref name="values"/> array, an exception will be thrown.
        /// </summary>
        /// <param name="values">An array of the backing memory.</param>
        /// <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"/> indicating whether the <paramref name="values"/> were pinned or not.</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static Tensor<T> Create<T>(T[] values, scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false)
        {
            return new Tensor<T>(values, lengths, strides, pinned);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with the data from <paramref name="values"/>.
        /// </summary>
        /// <param name="values">A <see cref="IEnumerable{T}"/> with the data to use for the initialization.</param>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        /// <param name="pinned">A <see cref="bool"/> indicating whether the <paramref name="values"/> were pinned or not.</param>
 
        public static Tensor<T> Create<T>(IEnumerable<T> values, scoped ReadOnlySpan<nint> lengths, bool pinned = false)
        {
            T[] data = values.ToArray();
            return new Tensor<T>(data, lengths.IsEmpty ? [data.Length] : lengths, pinned);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with the data from <paramref name="values"/>.
        /// </summary>
        /// <param name="values">A <see cref="IEnumerable{T}"/> with the data to use for the initialization.</param>
        /// <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"/> indicating whether the <paramref name="values"/> were pinned or not.</param>
        public static Tensor<T> Create<T>(IEnumerable<T> values, scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false)
        {
            T[] data = values.ToArray();
            return new Tensor<T>(data, lengths.IsEmpty ? [data.Length] : lengths, strides, pinned);
        }
 
        #region Normal
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with random data in a gaussian normal distribution.
        /// </summary>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        public static Tensor<T> CreateAndFillGaussianNormalDistribution<T>(params scoped ReadOnlySpan<nint> lengths)
            where T : IFloatingPoint<T>
        {
            return CreateAndFillGaussianNormalDistribution<T>(Random.Shared, lengths);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with random data in a gaussian normal distribution.
        /// </summary>
        /// <param name="random"></param>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        public static Tensor<T> CreateAndFillGaussianNormalDistribution<T>(Random random, params scoped ReadOnlySpan<nint> lengths)
            where T : IFloatingPoint<T>
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = new T[linearLength];
            GaussianDistribution<T>(values, linearLength, random);
            return new Tensor<T>(values, lengths, false);
        }
 
        private static void GaussianDistribution<T>(in Span<T> values, nint linearLength, Random random)
             where T : IFloatingPoint<T>
        {
            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
 
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with random data uniformly distributed.
        /// </summary>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        public static Tensor<T> CreateAndFillUniformDistribution<T>(params scoped ReadOnlySpan<nint> lengths)
            where T : IFloatingPoint<T>
        {
            return CreateAndFillUniformDistribution<T>(Random.Shared, lengths);
        }
 
        /// <summary>
        /// Creates a <see cref="Tensor{T}"/> and initializes it with random data uniformly distributed.
        /// </summary>
        /// <param name="random"></param>
        /// <param name="lengths">A <see cref="ReadOnlySpan{T}"/> indicating the lengths of each dimension.</param>
        public static Tensor<T> CreateAndFillUniformDistribution<T>(Random random, params scoped ReadOnlySpan<nint> lengths)
            where T : IFloatingPoint<T>
        {
            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<T>(values, lengths, false);
        }
 
        /// <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>
        public static Tensor<T> CreateUninitialized<T>(scoped ReadOnlySpan<nint> lengths, bool pinned = false)
            => CreateUninitialized<T>(lengths, [], 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>
        public static Tensor<T> CreateUninitialized<T>(scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false)
        {
            nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
            T[] values = GC.AllocateUninitializedArray<T>((int)linearLength, pinned);
            return new Tensor<T>(values, lengths, strides, pinned);
        }
 
        /// <summary>
        /// Fills the given <see cref="TensorSpan{T}"/> with random data in a Gaussian normal distribution. <see cref="System.Random"/>
        /// can optionally be provided for seeding.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="destination">The destination <see cref="TensorSpan{T}"/> where the data will be stored.</param>
        /// <param name="random"><see cref="System.Random"/> to provide random seeding. Defaults to <see cref="Random.Shared"/> if not provided.</param>
        /// <returns></returns>
        public static ref readonly TensorSpan<T> FillGaussianNormalDistribution<T>(in TensorSpan<T> destination, Random? random = null) where T : IFloatingPoint<T>
        {
            Span<T> span = MemoryMarshal.CreateSpan<T>(ref destination._reference, (int)destination._shape._memoryLength);
 
            GaussianDistribution<T>(span, destination._shape._memoryLength, random ?? Random.Shared);
 
            return ref destination;
        }
 
        /// <summary>
        /// Fills the given <see cref="TensorSpan{T}"/> with random data in a uniform distribution. <see cref="System.Random"/>
        /// can optionally be provided for seeding.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="destination">The destination <see cref="TensorSpan{T}"/> where the data will be stored.</param>
        /// <param name="random"><see cref="System.Random"/> to provide random seeding. Defaults to <see cref="Random.Shared"/> if not provided.</param>
        /// <returns></returns>
        public static ref readonly TensorSpan<T> FillUniformDistribution<T>(in TensorSpan<T> destination, Random? random = null) where T : IFloatingPoint<T>
        {
            Span<T> span = MemoryMarshal.CreateSpan<T>(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;
        }
    }
}