namespace SynorCompute; /// /// Multi-dimensional tensor for compute operations. /// /// /// /// // Create a 2D tensor /// var matrix = new Tensor(new[] { 2, 3 }, new[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 }); /// /// // Create random tensor /// var random = Tensor.Rand(512, 512); /// /// // Operations /// var mean = random.Mean(); /// var transposed = matrix.Transpose(); /// /// public class Tensor : IEquatable { public int[] Shape { get; } public double[] Data { get; } public Precision Dtype { get; } public int Size => Data.Length; public int Ndim => Shape.Length; public Tensor(int[] shape, double[] data, Precision dtype = Precision.Fp32) { int expectedSize = shape.Aggregate(1, (a, b) => a * b); if (data.Length != expectedSize) { throw new ArgumentException($"Data size {data.Length} does not match shape [{string.Join(", ", shape)}]"); } Shape = (int[])shape.Clone(); Data = (double[])data.Clone(); Dtype = dtype; } /// /// Get element at indices. /// public double this[params int[] indices] { get { if (indices.Length != Shape.Length) { throw new ArgumentException("Index dimensions must match tensor dimensions"); } int idx = 0; int stride = 1; for (int i = Shape.Length - 1; i >= 0; i--) { idx += indices[i] * stride; stride *= Shape[i]; } return Data[idx]; } } // Factory Methods public static Tensor Of(double[] data) => new([data.Length], data); public static Tensor Of(double[,] data) { int rows = data.GetLength(0); int cols = data.GetLength(1); var flat = new double[rows * cols]; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { flat[i * cols + j] = data[i, j]; } } return new Tensor([rows, cols], flat); } public static Tensor Zeros(params int[] shape) { int size = shape.Aggregate(1, (a, b) => a * b); return new Tensor(shape, new double[size]); } public static Tensor Ones(params int[] shape) { int size = shape.Aggregate(1, (a, b) => a * b); var data = new double[size]; Array.Fill(data, 1.0); return new Tensor(shape, data); } public static Tensor Rand(params int[] shape) { int size = shape.Aggregate(1, (a, b) => a * b); var random = new Random(); var data = new double[size]; for (int i = 0; i < size; i++) { data[i] = random.NextDouble(); } return new Tensor(shape, data); } public static Tensor Randn(params int[] shape) { int size = shape.Aggregate(1, (a, b) => a * b); var random = new Random(); var data = new double[size]; for (int i = 0; i < size; i++) { // Box-Muller transform double u1 = random.NextDouble(); double u2 = random.NextDouble(); data[i] = Math.Sqrt(-2 * Math.Log(u1)) * Math.Cos(2 * Math.PI * u2); } return new Tensor(shape, data); } public static Tensor Eye(int n) { var data = new double[n * n]; for (int i = 0; i < n; i++) { data[i * n + i] = 1.0; } return new Tensor([n, n], data); } public static Tensor Arange(double start, double end, double step = 1.0) { int size = (int)Math.Ceiling((end - start) / step); var data = new double[size]; for (int i = 0; i < size; i++) { data[i] = start + i * step; } return new Tensor([size], data); } public static Tensor Linspace(double start, double end, int num) { var data = new double[num]; double step = (end - start) / (num - 1); for (int i = 0; i < num; i++) { data[i] = start + i * step; } return new Tensor([num], data); } // Operations public Tensor Reshape(params int[] newShape) { int newSize = newShape.Aggregate(1, (a, b) => a * b); if (newSize != Size) { throw new ArgumentException($"Cannot reshape tensor of size {Size} to shape [{string.Join(", ", newShape)}]"); } return new Tensor(newShape, Data, Dtype); } public Tensor Transpose() { if (Ndim != 2) { throw new InvalidOperationException("Transpose only supported for 2D tensors"); } int rows = Shape[0]; int cols = Shape[1]; var transposed = new double[Data.Length]; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { transposed[j * rows + i] = Data[i * cols + j]; } } return new Tensor([cols, rows], transposed, Dtype); } // Reductions public double Mean() => Data.Average(); public double Sum() => Data.Sum(); public double Std() { double mean = Mean(); double sumSq = Data.Sum(x => (x - mean) * (x - mean)); return Math.Sqrt(sumSq / Data.Length); } public double Max() => Data.Max(); public double Min() => Data.Min(); // Activations public Tensor Relu() => new(Shape, Data.Select(x => Math.Max(0, x)).ToArray(), Dtype); public Tensor Sigmoid() => new(Shape, Data.Select(x => 1.0 / (1.0 + Math.Exp(-x))).ToArray(), Dtype); public Tensor Softmax() { double maxVal = Max(); var expValues = Data.Select(x => Math.Exp(x - maxVal)).ToArray(); double sum = expValues.Sum(); return new Tensor(Shape, expValues.Select(x => x / sum).ToArray(), Dtype); } // Conversion public object ToNestedList() { return Ndim switch { 1 => Data.ToList(), 2 => Enumerable.Range(0, Shape[0]) .Select(i => Enumerable.Range(0, Shape[1]) .Select(j => Data[i * Shape[1] + j]) .ToList()) .ToList(), _ => throw new InvalidOperationException("ToNestedList only supports 1D and 2D tensors") }; } // Equality public bool Equals(Tensor? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; return Shape.SequenceEqual(other.Shape) && Data.SequenceEqual(other.Data) && Dtype == other.Dtype; } public override bool Equals(object? obj) => Equals(obj as Tensor); public override int GetHashCode() => HashCode.Combine( Shape.Aggregate(0, HashCode.Combine), Data.Aggregate(0, (acc, val) => HashCode.Combine(acc, val.GetHashCode())), Dtype ); public override string ToString() => $"Tensor(shape=[{string.Join(", ", Shape)}], dtype={Dtype})"; }