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})";
}