using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using NumSharp.Utilities;
namespace NumSharp
{
///
/// Represents a shape of an N-D array.
///
/// Handles slicing, indexing based on coordinates or linear offset and broadcastted indexing.
public struct Shape : ICloneable, IEquatable
{
internal ViewInfo ViewInfo;
internal BroadcastInfo BroadcastInfo;
///
/// True if the shape of this array was obtained by a slicing operation that caused the underlying data to be non-contiguous
///
public bool IsSliced
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ViewInfo != null;
}
///
/// Is this Shape a recusive view? (deeper than 1 view)
///
public bool IsRecursive
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ViewInfo != null && ViewInfo.ParentShape.IsEmpty == false;
}
///
/// Dense data are stored contiguously in memory, addressed by a single index (the memory address).
/// Array memory ordering schemes translate that single index into multiple indices corresponding to the array coordinates.
/// 0: Row major
/// 1: Column major
///
internal char layout;
internal int _hashCode;
internal int size;
internal int[] dimensions;
internal int[] strides;
///
/// Is this shape a broadcast and/or has modified strides?
///
internal bool IsBroadcasted => BroadcastInfo != null;
///
/// Is this shape a scalar? (==0 && == 1)
///
public bool IsScalar;
///
/// True if the shape is not initialized.
/// Note: A scalar shape is not empty.
///
public bool IsEmpty => _hashCode == 0;
public char Order => layout;
///
/// Singleton instance of a that represents a scalar.
///
public static readonly Shape Scalar = new Shape(Array.Empty());
///
/// Create a new scalar shape
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Shape NewScalar() => new Shape(Array.Empty());
///
/// Create a new scalar shape
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Shape NewScalar(ViewInfo viewInfo) => new Shape(Array.Empty()) {ViewInfo = viewInfo};
///
/// Create a new scalar shape
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Shape NewScalar(ViewInfo viewInfo, BroadcastInfo broadcastInfo) => new Shape(Array.Empty()) {ViewInfo = viewInfo, BroadcastInfo = broadcastInfo};
///
/// Create a shape that represents a vector.
///
/// Faster than calling Shape's constructor
public static Shape Vector(int length)
{
var shape = new Shape {dimensions = new int[] {length}, strides = new int[] {1}, layout = 'C', size = length};
shape._hashCode = (shape.layout * 397) ^ (length * 397) * (length * 397);
return shape;
}
///
/// Create a shape that represents a vector.
///
/// Faster than calling Shape's constructor
public static Shape Vector(int length, ViewInfo viewInfo)
{
var shape = new Shape
{
dimensions = new[] {length},
strides = new int[] {1},
layout = 'C',
size = length,
ViewInfo = viewInfo
};
shape._hashCode = (shape.layout * 397) ^ (length * 397) * (length * 397);
return shape;
}
///
/// Create a shape that represents a matrix.
///
/// Faster than calling Shape's constructor
public static Shape Matrix(int rows, int cols)
{
var shape = new Shape {dimensions = new[] {rows, cols}, strides = new int[] {cols, 1}, layout = 'C', size = rows * cols};
unchecked
{
int hash = (shape.layout * 397);
int size = 1;
foreach (var v in shape.dimensions)
{
size *= v;
hash ^= (size * 397) * (v * 397);
}
shape._hashCode = hash;
}
shape.IsScalar = false;
return shape;
}
public int NDim
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => dimensions.Length;
}
public int[] Dimensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => dimensions;
}
public int[] Strides
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => strides;
}
///
/// The linear size of this shape.
///
public int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => size;
}
public Shape(Shape other)
{
if (other.IsEmpty)
{
this = default;
return;
}
this.layout = other.layout;
this._hashCode = other._hashCode;
this.size = other.size;
this.dimensions = (int[])other.dimensions.Clone();
this.strides = (int[])other.strides.Clone();
this.IsScalar = other.IsScalar;
this.ViewInfo = other.ViewInfo?.Clone();
this.BroadcastInfo = other.BroadcastInfo;
}
public Shape(int[] dims, int[] strides)
{
if (dims == null)
throw new ArgumentNullException(nameof(dims));
if (strides == null)
throw new ArgumentNullException(nameof(strides));
if (dims.Length != strides.Length)
throw new ArgumentException($"While trying to construct a shape, given dimensions and strides does not match size ({dims.Length} != {strides.Length})");
layout = 'C';
size = 1;
unchecked
{
//calculate hash and size
if (dims.Length > 0)
{
int hash = (layout * 397);
foreach (var v in dims)
{
size *= v;
hash ^= (size * 397) * (v * 397);
}
_hashCode = hash;
}
else
_hashCode = 0;
}
this.strides = strides;
this.dimensions = dims;
IsScalar = size == 1 && dims.Length == 0;
ViewInfo = null;
BroadcastInfo = null;
}
public Shape(int[] dims, int[] strides, Shape originalShape)
{
if (dims == null)
throw new ArgumentNullException(nameof(dims));
if (strides == null)
throw new ArgumentNullException(nameof(strides));
if (dims.Length != strides.Length)
throw new ArgumentException($"While trying to construct a shape, given dimensions and strides does not match size ({dims.Length} != {strides.Length})");
layout = 'C';
size = 1;
unchecked
{
//calculate hash and size
if (dims.Length > 0)
{
int hash = (layout * 397);
foreach (var v in dims)
{
size *= v;
hash ^= (size * 397) * (v * 397);
}
_hashCode = hash;
}
else
_hashCode = 0;
}
this.strides = strides;
this.dimensions = dims;
IsScalar = size == 1 && dims.Length == 0;
ViewInfo = null;
BroadcastInfo = new BroadcastInfo() {OriginalShape = originalShape};
}
[MethodImpl((MethodImplOptions)512)]
public Shape(params int[] dims)
{
if (dims == null)
{
strides = dims = dimensions = Array.Empty();
}
else
{
dimensions = dims;
strides = new int[dims.Length];
}
unchecked
{
size = 1;
layout = 'C';
if (dims.Length > 0)
{
int hash = (layout * 397);
foreach (var v in dims)
{
size *= v;
hash ^= (size * 397) * (v * 397);
}
_hashCode = hash;
}
else
_hashCode = int.MinValue; //scalar's hashcode is int.minvalue
if (dims.Length != 0)
if (layout == 'C')
{
strides[strides.Length - 1] = 1;
for (int i = strides.Length - 1; i >= 1; i--)
strides[i - 1] = strides[i] * dims[i];
}
else
{
strides[0] = 1;
for (int idx = 1; idx < strides.Length; idx++)
strides[idx] = strides[idx - 1] * dims[idx - 1];
}
}
IsScalar = _hashCode == int.MinValue;
ViewInfo = null;
BroadcastInfo = null;
}
///
/// An empty shape without any fields set except all are default.
///
/// Used internally.
[MethodImpl((MethodImplOptions)768)]
public static Shape Empty(int ndim)
{
return new Shape {dimensions = new int[ndim], strides = new int[ndim]};
//default vals already sets: ret.layout = 0;
//default vals already sets: ret.size = 0;
//default vals already sets: ret._hashCode = 0;
//default vals already sets: ret.IsScalar = false;
//default vals already sets: ret.ViewInfo = null;
}
[MethodImpl((MethodImplOptions)768)]
private void _computeStrides()
{
if (dimensions.Length == 0)
return;
unchecked
{
if (layout == 'C')
{
strides[strides.Length - 1] = 1;
for (int idx = strides.Length - 1; idx >= 1; idx--)
strides[idx - 1] = strides[idx] * dimensions[idx];
}
else
{
strides[0] = 1;
for (int idx = 1; idx < strides.Length; idx++)
strides[idx] = strides[idx - 1] * dimensions[idx - 1];
}
}
}
public int this[int dim]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => dimensions[dim < 0 ? dimensions.Length + dim : dim];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => dimensions[dim < 0 ? dimensions.Length + dim : dim] = value;
}
///
/// Retrieve the transformed offset if is true, otherwise returns .
///
/// The offset within the bounds of .
/// The transformed offset.
/// Avoid using unless it is unclear if shape is sliced or not.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int TransformOffset(int offset)
{
// ReSharper disable once ConvertIfStatementToReturnStatement
if (ViewInfo == null && BroadcastInfo == null)
return offset;
return GetOffset(GetCoordinates(offset));
}
///
/// Get offset index out of coordinate indices.
///
/// The coordinates to turn into linear offset
/// The index in the memory block that refers to a specific value.
/// Handles sliced indices and broadcasting
[MethodImpl((MethodImplOptions)768)]
public int GetOffset(params int[] indices)
{
if (!IsSliced)
return GetOffset_IgnoreViewInfo(indices);
//if both sliced and broadcasted
if (IsBroadcasted)
return GetOffset_broadcasted(indices);
// we are dealing with a slice
int offset;
var vi = ViewInfo;
if (IsRecursive && vi.Slices == null)
{
// we are dealing with an unsliced recursively reshaped slice
offset = GetOffset_IgnoreViewInfo(indices);
var parent_coords = vi.ParentShape.GetCoordinates(offset, ignore_view_info: true);
return vi.ParentShape.GetOffset(parent_coords);
}
var coords = new List(indices);
if (vi.UnreducedShape.IsScalar && indices.Length == 1 && indices[0] == 0 && !IsRecursive)
return 0;
if (indices.Length > vi.UnreducedShape.dimensions.Length)
throw new ArgumentOutOfRangeException(nameof(indices), $"select has too many coordinates for this shape");
var orig_ndim = vi.OriginalShape.NDim;
if (orig_ndim > NDim && orig_ndim > indices.Length)
{
// fill in reduced dimensions in the provided coordinates
for (int i = 0; i < vi.OriginalShape.NDim; i++)
{
var slice = ViewInfo.Slices[i];
if (slice.IsIndex)
coords.Insert(i, 0);
if (coords.Count == orig_ndim)
break;
}
}
var orig_strides = vi.OriginalShape.strides;
var orig_dims = vi.OriginalShape.dimensions;
offset = 0;
unchecked
{
for (int i = 0; i < coords.Count; i++)
{
// note: we can refrain from bounds checking here, because we should not allow negative indices at all, this should be checked higher up though.
//var coord = coords[i];
//var dim = orig_dims[i];
//if (coord < -dim || coord >= dim)
// throw new ArgumentException($"index {coord} is out of bounds for axis {i} with a size of {dim}");
//if (coord < 0)
// coord = dim + coord;
if (vi.Slices.Length <= i)
{
offset += orig_strides[i] * coords[i];
continue;
}
var slice = vi.Slices[i];
var start = slice.Start;
if (slice.IsIndex)
offset += orig_strides[i] * start; // the coord is irrelevant for index-slices (they are reduced dimensions)
else
offset += orig_strides[i] * (start + coords[i] * slice.Step);
}
}
if (!IsRecursive)
return offset;
// we are dealing with a sliced recursively reshaped slice
var parent_coords1 = vi.ParentShape.GetCoordinates(offset, ignore_view_info: true);
return vi.ParentShape.GetOffset(parent_coords1);
}
///
/// Calculate the offset in an unsliced shape. If the shape is sliced, ignore the ViewInfo
/// Note: to be used only inside of GetOffset()
///
[MethodImpl((MethodImplOptions)768)]
private int GetOffset_IgnoreViewInfo(params int[] indices)
{
if (dimensions.Length == 0 && indices.Length == 1)
return indices[0];
int offset = 0;
unchecked
{
for (int i = 0; i < indices.Length; i++)
offset += strides[i] * indices[i];
}
if (IsBroadcasted)
return offset % BroadcastInfo.OriginalShape.size;
return offset;
}
///
/// Get offset index out of coordinate indices.
///
/// The coordinates to turn into linear offset
/// The index in the memory block that refers to a specific value.
/// Handles sliced indices and broadcasting
[MethodImpl((MethodImplOptions)768)]
private int GetOffset_broadcasted(params int[] indices)
{
int offset;
var vi = ViewInfo;
var bi = BroadcastInfo;
if (IsRecursive && vi.Slices == null)
{
// we are dealing with an unsliced recursively reshaped slice
offset = GetOffset_IgnoreViewInfo(indices);
var parent_coords = vi.ParentShape.GetCoordinates(offset, ignore_view_info: true);
return vi.ParentShape.GetOffset(parent_coords);
}
var coords = new List(indices);
if (vi.UnreducedShape.IsScalar && indices.Length == 1 && indices[0] == 0 && !IsRecursive)
return 0;
if (indices.Length > vi.UnreducedShape.dimensions.Length)
throw new ArgumentOutOfRangeException(nameof(indices), $"select has too many coordinates for this shape");
var orig_ndim = vi.OriginalShape.NDim;
if (orig_ndim > NDim && orig_ndim > indices.Length)
{
// fill in reduced dimensions in the provided coordinates
for (int i = 0; i < vi.OriginalShape.NDim; i++)
{
var slice = ViewInfo.Slices[i];
if (slice.IsIndex)
coords.Insert(i, 0);
if (coords.Count == orig_ndim)
break;
}
}
var orig_strides = vi.OriginalShape.strides;
Shape unreducedBroadcasted;
if (!bi.UnbroadcastShape.HasValue)
{
if (bi.OriginalShape.IsScalar)
{
unreducedBroadcasted = vi.OriginalShape.Clone(true, false, false);
for (int i = 0; i < unreducedBroadcasted.NDim; i++)
{
unreducedBroadcasted.dimensions[i] = 1;
unreducedBroadcasted.strides[i] = 0;
}
}
else
{
unreducedBroadcasted = vi.OriginalShape.Clone(true, false, false);
for (int i = Math.Abs(vi.OriginalShape.NDim - NDim), j = 0; i < unreducedBroadcasted.NDim; i++, j++)
{
if (strides[j] == 0)
{
unreducedBroadcasted.dimensions[i] = 1;
unreducedBroadcasted.strides[i] = 0;
}
}
}
bi.UnbroadcastShape = unreducedBroadcasted;
}
else
unreducedBroadcasted = bi.UnbroadcastShape.Value;
orig_strides = unreducedBroadcasted.strides;
offset = 0;
unchecked
{
for (int i = 0; i < coords.Count; i++)
{
if (vi.Slices.Length <= i)
{
offset += orig_strides[i] * coords[i];
continue;
}
var slice = vi.Slices[i];
var start = slice.Start;
if (slice.IsIndex)
offset += orig_strides[i] * start; // the coord is irrelevant for index-slices (they are reduced dimensions)
else
offset += orig_strides[i] * (start + coords[i] * slice.Step);
}
}
if (!IsRecursive)
return offset;
// we are dealing with a sliced recursively reshaped slice
var parent_coords1 = vi.ParentShape.GetCoordinates(offset, ignore_view_info: true);
return vi.ParentShape.GetOffset(parent_coords1);
}
///
/// Gets the shape based on given and the index offset (C-Contiguous) inside the current storage.
///
/// The selection of indexes 0 based.
///
/// Used for slicing, returned shape is the new shape of the slice and offset is the offset from current address.
[MethodImpl((MethodImplOptions)768)]
public (Shape Shape, int Offset) GetSubshape(params int[] indicies)
{
if (indicies.Length == 0)
return (this, 0);
int offset;
var dim = indicies.Length;
var newNDim = dimensions.Length - dim;
if (IsBroadcasted)
{
indicies = (int[])indicies.Clone(); //we must copy because we make changes to it.
Shape unreducedBroadcasted;
if (!BroadcastInfo.UnbroadcastShape.HasValue)
{
unreducedBroadcasted = this.Clone(true, false, false);
for (int i = 0; i < unreducedBroadcasted.NDim; i++)
{
if (unreducedBroadcasted.strides[i] == 0)
unreducedBroadcasted.dimensions[i] = 1;
}
BroadcastInfo.UnbroadcastShape = unreducedBroadcasted;
}
else
unreducedBroadcasted = BroadcastInfo.UnbroadcastShape.Value;
//unbroadcast indices
for (int i = 0; i < dim; i++)
indicies[i] = indicies[i] % unreducedBroadcasted[i];
offset = unreducedBroadcasted.GetOffset(indicies);
var retShape = new int[newNDim];
var strides = new int[newNDim];
var original = new int[newNDim];
var original_strides = new int[newNDim];
for (int i = 0; i < newNDim; i++)
{
retShape[i] = this.dimensions[dim + i];
strides[i] = this.strides[dim + i];
original[i] = unreducedBroadcasted[dim + i];
original_strides[i] = unreducedBroadcasted.strides[dim + i];
}
return (new Shape(retShape, strides, new Shape(original, original_strides)), offset);
}
//compute offset
offset = GetOffset(indicies);
var orig_shape = IsSliced ? ViewInfo.OriginalShape : this;
if (offset >= orig_shape.Size)
throw new IndexOutOfRangeException($"The offset {offset} is out of range in Shape {orig_shape.Size}");
if (indicies.Length == dimensions.Length)
return (Scalar, offset);
//compute subshape
var innerShape = new int[newNDim];
for (int i = 0; i < innerShape.Length; i++)
innerShape[i] = this.dimensions[dim + i];
//TODO! This is not full support of sliced,
//TODO! when sliced it usually diverts from this function but it would be better if we add support for sliced arrays too.
return (new Shape(innerShape), offset);
}
///
/// Transforms offset index into coordinates that matches this shape.
///
///
///
[MethodImpl((MethodImplOptions)768)]
public int[] GetCoordinates(int offset, bool ignore_view_info = false)
{
int[] coords = null;
if (strides.Length == 1)
coords = new int[] {offset};
else if (layout == 'C')
{
int counter = offset;
coords = new int[strides.Length];
int stride;
for (int i = 0; i < strides.Length; i++)
{
stride = strides[i];
if (stride == 0)
{
coords[i] = 0;
}
else
{
coords[i] = counter / stride;
counter -= coords[i] * stride;
}
}
}
else
{
int counter = offset;
coords = new int[strides.Length];
int stride;
for (int i = strides.Length - 1; i >= 0; i--)
{
stride = strides[i];
if (stride == 0)
{
coords[i] = 0;
}
else
{
coords[i] = counter / stride;
counter -= coords[i] * stride;
}
}
}
if (IsSliced && !ignore_view_info)
{
// TODO! undo dimensionality reduction
for (int i = 0; i < coords.Length; i++)
{
var slice = ViewInfo.Slices[i];
coords[i] = (coords[i] / slice.Step) - slice.Start;
}
}
return coords;
}
[MethodImpl((MethodImplOptions)768)]
public void ChangeTensorLayout(char order = 'C')
{
layout = order;
_computeStrides();
ComputeHashcode();
}
[MethodImpl((MethodImplOptions)768)]
public static int GetSize(int[] dims)
{
int size = 1;
unchecked
{
for (int i = 0; i < dims.Length; i++)
size *= dims[i];
}
return size;
}
public static int[] GetAxis(ref Shape shape, int axis)
{
return GetAxis(shape.dimensions, axis);
}
public static int[] GetAxis(Shape shape, int axis)
{
return GetAxis(shape.dimensions, axis);
}
public static int[] GetAxis(int[] dims, int axis)
{
if (dims == null)
throw new ArgumentNullException(nameof(dims));
if (dims.Length == 0)
return Array.Empty();
if (axis <= -1) axis = dims.Length - 1;
if (axis >= dims.Length)
throw new AxisOutOfRangeException(dims.Length, axis);
return dims.RemoveAt(axis);
}
///
/// Extracts the shape of given .
///
/// Supports both jagged and multi-dim.
[MethodImpl((MethodImplOptions)512)]
public static int[] ExtractShape(Array array)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
bool isJagged = false;
{
var type = array.GetType();
isJagged = array.Rank == 1 && type.IsArray && type.GetElementType().IsArray;
}
var l = new List(16);
if (isJagged)
{
// ReSharper disable once PossibleNullReferenceException
Array arr = array;
do
{
l.Add(arr.Length);
arr = arr.GetValue(0) as Array;
} while (arr != null && arr.GetType().IsArray);
}
else
{
//jagged or regular
for (int dim = 0; dim < array.Rank; dim++)
{
l.Add(array.GetLength(dim));
}
}
return l.ToArray();
}
///
/// Recalculate hashcode from current dimension and layout.
///
[MethodImpl((MethodImplOptions)768)]
internal void ComputeHashcode()
{
if (dimensions.Length > 0)
{
unchecked
{
size = 1;
int hash = (layout * 397);
foreach (var v in dimensions)
{
size *= v;
hash ^= (size * 397) * (v * 397);
}
_hashCode = hash;
}
}
}
#region Slicing support
[MethodImpl((MethodImplOptions)768)]
public Shape Slice(string slicing_notation) => this.Slice(NumSharp.Slice.ParseSlices(slicing_notation));
[MethodImpl((MethodImplOptions)768)]
public Shape Slice(params Slice[] input_slices)
{
if (IsEmpty)
throw new InvalidOperationException("Unable to slice an empty shape.");
//if (IsBroadcasted)
// throw new NotSupportedException("Unable to slice a shape that is broadcasted.");
var slices = new List(16);
var sliced_axes_unreduced = new List();
for (int i = 0; i < NDim; i++)
{
var dim = Dimensions[i];
var slice = input_slices.Length > i ? input_slices[i] : NumSharp.Slice.All; //fill missing selectors
var slice_def = slice.ToSliceDef(dim);
slices.Add(slice_def);
var count = Math.Abs(slices[i].Count); // for index-slices count would be -1 but we need 1.
sliced_axes_unreduced.Add(count);
}
if (IsSliced && ViewInfo.Slices != null)
{
// merge new slices with existing ones and insert the indices of the parent shape that were previously reduced
for (int i = 0; i < ViewInfo.OriginalShape.NDim; i++)
{
var orig_slice = ViewInfo.Slices[i];
if (orig_slice.IsIndex)
{
slices.Insert(i, orig_slice);
sliced_axes_unreduced.Insert(i, 1);
continue;
}
slices[i] = ViewInfo.Slices[i].Merge(slices[i]);
sliced_axes_unreduced[i] = Math.Abs(slices[i].Count);
}
}
var sliced_axes = sliced_axes_unreduced.Where((dim, i) => !slices[i].IsIndex).ToArray();
var origin = (this.IsSliced && ViewInfo.Slices != null) ? this.ViewInfo.OriginalShape : this;
var viewInfo = new ViewInfo() {OriginalShape = origin, Slices = slices.ToArray(), UnreducedShape = new Shape(sliced_axes_unreduced.ToArray()),};
if (IsRecursive)
viewInfo.ParentShape = ViewInfo.ParentShape;
if (sliced_axes.Length == 0) //is it a scalar
return NewScalar(viewInfo);
return new Shape(sliced_axes) {ViewInfo = viewInfo};
}
#endregion
#region Implicit Operators
public static explicit operator int[](Shape shape) => (int[])shape.dimensions.Clone(); //we clone to avoid any changes
public static implicit operator Shape(int[] dims) => new Shape(dims);
public static explicit operator int(Shape shape) => shape.Size;
public static explicit operator Shape(int dim) => Shape.Vector(dim);
public static explicit operator (int, int)(Shape shape) => shape.dimensions.Length == 2 ? (shape.dimensions[0], shape.dimensions[1]) : (0, 0); //TODO! this should return (0,0) but rather (dim[0], dim[1]) regardless of size.
public static implicit operator Shape((int, int) dims) => Shape.Matrix(dims.Item1, dims.Item2);
public static explicit operator (int, int, int)(Shape shape) => shape.dimensions.Length == 3 ? (shape.dimensions[0], shape.dimensions[1], shape.dimensions[2]) : (0, 0, 0);
public static implicit operator Shape((int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3);
public static explicit operator (int, int, int, int)(Shape shape) => shape.dimensions.Length == 4 ? (shape.dimensions[0], shape.dimensions[1], shape.dimensions[2], shape.dimensions[3]) : (0, 0, 0, 0);
public static implicit operator Shape((int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4);
public static explicit operator (int, int, int, int, int)(Shape shape) => shape.dimensions.Length == 5 ? (shape.dimensions[0], shape.dimensions[1], shape.dimensions[2], shape.dimensions[3], shape.dimensions[4]) : (0, 0, 0, 0, 0);
public static implicit operator Shape((int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5);
public static explicit operator (int, int, int, int, int, int)(Shape shape) => shape.dimensions.Length == 6 ? (shape.dimensions[0], shape.dimensions[1], shape.dimensions[2], shape.dimensions[3], shape.dimensions[4], shape.dimensions[5]) : (0, 0, 0, 0, 0, 0);
public static implicit operator Shape((int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6);
#endregion
#region Deconstructor
public void Deconstruct(out int dim1, out int dim2)
{
var dims = this.dimensions;
dim1 = dims[0];
dim2 = dims[1];
}
public void Deconstruct(out int dim1, out int dim2, out int dim3)
{
var dims = this.dimensions;
dim1 = dims[0];
dim2 = dims[1];
dim3 = dims[2];
}
public void Deconstruct(out int dim1, out int dim2, out int dim3, out int dim4)
{
var dims = this.dimensions;
dim1 = dims[0];
dim2 = dims[1];
dim3 = dims[2];
dim4 = dims[3];
}
public void Deconstruct(out int dim1, out int dim2, out int dim3, out int dim4, out int dim5)
{
var dims = this.dimensions;
dim1 = dims[0];
dim2 = dims[1];
dim3 = dims[2];
dim4 = dims[3];
dim5 = dims[4];
}
public void Deconstruct(out int dim1, out int dim2, out int dim3, out int dim4, out int dim5, out int dim6)
{
var dims = this.dimensions;
dim1 = dims[0];
dim2 = dims[1];
dim3 = dims[2];
dim4 = dims[3];
dim5 = dims[4];
dim6 = dims[5];
}
#endregion
#region Equality
public static bool operator ==(Shape a, Shape b)
{
if (a.IsEmpty && b.IsEmpty)
return true;
if (a.IsEmpty || b.IsEmpty)
return false;
if (a.size != b.size || a.NDim != b.NDim)
return false;
var dim = a.NDim;
for (int i = 0; i < dim; i++)
{
if (a[i] != b[i])
return false;
}
return true;
}
public static bool operator !=(Shape a, Shape b)
{
return !(a == b);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (obj.GetType() != this.GetType())
{
return false;
}
return Equals((Shape)obj);
}
/// Indicates whether the current object is equal to another object of the same type.
/// An object to compare with this object.
/// true if the current object is equal to the other parameter; otherwise, false.
public bool Equals(Shape other)
{
if ((_hashCode == 0 && _hashCode == other._hashCode) || dimensions == null && other.dimensions == null) //they are empty.
return true;
if ((dimensions == null && other.dimensions != null) || (dimensions != null && other.dimensions == null)) //they are empty.
return false;
if (size != other.size || layout != other.layout || dimensions.Length != other.dimensions.Length)
return false;
// ReSharper disable once LoopCanBeConvertedToQuery
for (int i = 0; i < dimensions.Length; i++)
{
if (dimensions[i] != other.dimensions[i])
{
return false;
}
}
return true;
}
/// Serves as the default hash function.
/// A hash code for the current object.
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return _hashCode;
}
#endregion
///
/// Expands a specific with 1 dimension.
///
///
///
[SuppressMessage("ReSharper", "LocalVariableHidesMember")]
internal Shape ExpandDimension(int axis)
{
Shape ret;
if (IsScalar)
{
ret = Vector(1);
ret.strides[0] = 0;
}
else
{
ret = Clone(true, true, false);
}
var dimensions = ret.dimensions;
var strides = ret.strides;
// Allow negative axis specification
if (axis < 0)
{
axis = dimensions.Length + 1 + axis;
if (axis < 0)
{
throw new ArgumentException($"Effective axis {axis} is less than 0");
}
}
Arrays.Insert(ref dimensions, axis, 1);
Arrays.Insert(ref strides, axis, 0);
ret.dimensions = dimensions;
ret.strides = strides;
if (IsSliced)
{
ret.ViewInfo = new ViewInfo() {ParentShape = this, Slices = null};
}
ret.ComputeHashcode();
return ret;
}
///
/// Translates coordinates with negative indices, e.g:
/// np.arange(9)[-1] == np.arange(9)[8]
/// np.arange(9)[-2] == np.arange(9)[7]
///
/// The dimensions these coordinates are targeting
/// The coordinates.
/// Coordinates without negative indices.
[SuppressMessage("ReSharper", "ParameterHidesMember"), MethodImpl((MethodImplOptions)512)]
public static int[] InferNegativeCoordinates(int[] dimensions, int[] coords)
{
for (int i = 0; i < coords.Length; i++)
{
var curr = coords[i];
if (curr < 0)
coords[i] = dimensions[i] + curr;
}
return coords;
}
public override string ToString() => "(" + string.Join(", ", dimensions) + ")";
/// Creates a new object that is a copy of the current instance.
/// A new object that is a copy of this instance.
object ICloneable.Clone() => Clone(true, false, false);
///
/// Creates a complete copy of this Shape.
///
/// Should make a complete deep clone or a shallow if false.
public Shape Clone(bool deep = true, bool unview = false, bool unbroadcast = false)
{
if (IsEmpty)
return default;
if (IsScalar)
{
if (unview || ViewInfo == null && BroadcastInfo == null)
return Scalar;
return NewScalar(ViewInfo?.Clone(), BroadcastInfo?.Clone());
}
if (!deep && !unview)
return this; //basic struct reassign
var ret = deep ? new Shape(this) : (Shape)MemberwiseClone();
if (unview)
ret.ViewInfo = null;
if (unbroadcast)
ret.BroadcastInfo = null;
return ret;
}
///
/// Returns a clean shape based on this.
/// Cleans ViewInfo and returns a newly constructed.
///
///
public Shape Clean()
{
if (IsScalar)
return NewScalar();
return new Shape((int[])this.dimensions.Clone());
}
}
}