private void PrettyPrint(StringBuilder s, bool flat = false) { if (Shape.Dimensions.Length == 0) { s.Append($"{GetValue(0)}"); return; } if (Shape.Dimensions.Length == 1) { s.Append("["); s.Append(string.Join(", ", this.GetData().OfType <object>().Select(x => x == null ? "null" : x.ToString()))); s.Append("]"); return; } var last_dim = Shape.Dimensions.Last(); var slices = new Slice[Shape.Dimensions.Length]; s.Append("["); for (int i = 0; i < last_dim; i++) { slices[0] = Slice.Index(i); var n_minus_one_dim_slice = new ViewStorage(this, slices); n_minus_one_dim_slice.PrettyPrint(s, flat); if (i < last_dim - 1) { s.Append(", "); if (!flat) { s.AppendLine(); } } } s.Append("]"); }
private void PrettyPrint(StringBuilder s, bool flat = false) { if (shape.Length == 0) { s.Append($"{Storage.GetData().GetValue(0)}"); return; } if (shape.Length == 1) { s.Append("["); s.Append(string.Join(", ", this.Array.OfType <object>().Select(x => x == null ? "null" : x.ToString()))); s.Append("]"); return; } var size = shape[0]; s.Append("["); for (int i = 0; i < size; i++) { var n_minus_one_dim_slice = this[Slice.Index(i)]; n_minus_one_dim_slice.PrettyPrint(s, flat); if (i < size - 1) { s.Append(", "); if (!flat) { s.AppendLine(); } } } s.Append("]"); }
protected void SetIndices(object[] indicesObjects, NDArray values) { var indicesLen = indicesObjects.Length; if (indicesLen == 1) { switch (indicesObjects[0]) { case NDArray nd: SetIndices(this, new NDArray[] {nd}, values); return; case int i: Storage.SetData(values, i); return; case bool boolean: if (boolean == false) return; //do nothing SetData(values); return; // np.expand_dims(this, 0); //equivalent to [np.newaxis] case int[] coords: SetData(values, coords); return; case NDArray[] nds: this[nds] = values; return; case object[] objs: this[objs] = values; return; case string slicesStr: new NDArray(Storage.GetView(Slice.ParseSlices(slicesStr))).SetData(values); return; case null: throw new ArgumentNullException($"The 1th dimension in given indices is null."); //no default } } int ints = 0; int bools = 0; for (var i = 0; i < indicesObjects.Length; i++) { switch (indicesObjects[i]) { case NDArray _: case int[] _: goto _NDArrayFound; case int _: ints++; continue; case bool @bool: bools++; continue; case string _: case Slice _: continue; case null: throw new ArgumentNullException($"The {i}th dimension in given indices is null."); default: throw new ArgumentException($"Unsupported indexing type: '{(indicesObjects[i]?.GetType()?.Name ?? "null")}'"); } } //handle all ints if (ints == indicesLen) { Storage.SetData(values, indicesObjects.Cast<int>().ToArray()); return; } //handle all booleans if (bools == indicesLen) { this[np.array(indicesObjects.Cast<bool>().ToArray(), false).MakeGeneric<bool>()] = values; return; } Slice[] slices; //handle regular slices try { slices = indicesObjects.Select(x => { switch (x) { case Slice o: return o; case int o: return Slice.Index(o); case string o: return new Slice(o); case bool o: return o ? Slice.NewAxis : throw new NumSharpException("false bool detected"); //TODO: verify this case IConvertible o: return Slice.Index((int)o.ToInt32(CultureInfo.InvariantCulture)); default: throw new ArgumentException($"Unsupported slice type: '{(x?.GetType()?.Name ?? "null")}'"); } }).ToArray(); } catch (NumSharpException e) when (e.Message.Contains("false bool detected")) { //handle rare case of false bool return; } new NDArray(Storage.GetView(slices)).SetData(values); //handle complex ndarrays indexing _NDArrayFound: var @this = this; var indices = new List<NDArray>(); bool foundNewAxis = false; int countNewAxes = 0; //handle ndarray indexing bool hasCustomExpandedSlice = false; //use for premature slicing detection for (int i = 0; i < indicesLen; i++) { var idx = indicesObjects[i]; _recuse: switch (idx) { case Slice o: if (o.IsEllipsis) { indicesObjects = ExpandEllipsis(indicesObjects, @this.ndim).ToArray(); //TODO: i think we need to set here indicesLen = indicesObjects.Length continue; } if (o.IsNewAxis) { //TODO: whats the approach to handling a newaxis in setter, findout. countNewAxes++; foundNewAxis = true; continue; } hasCustomExpandedSlice = true; indices.Add(GetIndicesFromSlice(@this.Shape.dimensions, o, i - countNewAxes)); continue; case int o: indices.Add(NDArray.Scalar<int>(o)); continue; case string o: indicesObjects[i] = idx = new Slice(o); goto _recuse; case bool o: if (o) { indicesObjects[i] = idx = Slice.NewAxis; goto _recuse; } else return; //false bool causes nullification of return. case IConvertible o: indices.Add(NDArray.Scalar<int>(o.ToInt32(CultureInfo.InvariantCulture))); continue; case int[] o: indices.Add(np.array(o, copy: false)); //we dont copy, pinning will be freed automatically after we done indexing. continue; case NDArray nd: if (nd.typecode == NPTypeCode.Boolean) { //TODO: mask only specific axis??? find a unit test to check it against. throw new Exception("if (nd.typecode == NPTypeCode.Boolean)"); } indices.Add(nd); continue; default: throw new ArgumentException($"Unsupported slice type: '{(idx?.GetType()?.Name ?? "null")}'"); } } NDArray[] indicesArray = indices.ToArray(); //handle premature slicing when the shapes cant be broadcasted together if (hasCustomExpandedSlice && !np.are_broadcastable(indicesArray)) { var ndim = indicesObjects.Length; var prematureSlices = new Slice[ndim]; var dims = @this.shape; for (int i = 0; i < ndim; i++) { if (indicesObjects[i] is Slice slice) { prematureSlices[i] = slice; //todo: we might need this in the future indicesObjects[i] = Slice.All; } else { prematureSlices[i] = Slice.All; } } @this = @this[prematureSlices]; //updated premature axes dims = @this.shape; for (int i = 0; i < ndim; i++) { if (prematureSlices[i] != Slice.All) { indicesArray[i] = GetIndicesFromSlice(dims, Slice.All, i); } } } //TODO: we can use a slice as null indice instead of expanding it, then we use PrepareIndexGetters to actually simulate that. SetIndices(@this, indicesArray, values); //TODO: this is valid code for getter, we need to impl a similar technique before passing @this. //if (foundNewAxis) //{ // //TODO: This is not the behavior when setting with new axis, is it even possible? // var targettedAxis = indices.Count - 1; // var axisOffset = this.ndim - targettedAxis; // var retShape = ret.Shape; // for (int i = 0; i < indicesLen; i++) // { // if (!(indicesObjects[i] is Slice slc) || !slc.IsNewAxis) // continue; // // var axis = Math.Max(0, Math.Min(i - axisOffset, ret.ndim)); // retShape = retShape.ExpandDimension(axis); // } // // ret = ret.reshape(retShape); //} // //return ret; }
/// <summary> /// Join a sequence of arrays along an existing axis. /// </summary> /// <param name="axis">The axis along which the arrays will be joined. If axis is None, arrays are flattened before use. Default is 0.</param> /// <param name="arrays">The arrays must have the same shape, except in the dimension corresponding to axis (the first, by default).</param> /// <returns>The concatenated array.</returns> /// <remarks>https://docs.scipy.org/doc/numpy/reference/generated/numpy.concatenate.html</remarks> public static NDArray concatenate(NDArray[] arrays, int axis = 0) { //What we do is we have the axis which is the only dimension that is allowed to be different //We need to perform a check if the dimensions actually match. //After we have the axis ax=1 where shape is (3,ax,3) - ax is the only dimension that can vary. //So if we got input of (3,5,3) and (3,1,3), we create a return shape of (3,6,3). //We perform the assignment by iterating a slice: (:,n,:) on src and dst where dst while n of dst grows as we iterate all arrays. if (arrays == null) { throw new ArgumentNullException(nameof(arrays)); } if (arrays.Length == 0) { throw new ArgumentException("Value cannot be an empty collection.", nameof(arrays)); } if (arrays.Length == 1) { return(arrays[0]); } var first = arrays[0]; var firstShape = (int[])first.shape.Clone(); while (axis < 0) { axis = first.ndim + axis; //translate negative axis } int i, j; int axisSize = 0; //accumulated shape[axis] size for return shape. NPTypeCode retType = first.GetTypeCode; foreach (var src in arrays) { //accumulate the concatenated axis var shape = src.shape; axisSize += shape[axis]; if (ReferenceEquals(src, first)) { continue; } var srcType = src.GetTypeCode; //resolve what the return type should be and should we perform casting. if (first.GetTypeCode != srcType) { if (srcType.CompareTo(retType) == 1) { retType = srcType; } } if (shape.Length != first.ndim) { throw new IncorrectShapeException("all the input arrays must have same number of dimensions."); } //verify the shapes are equal for (j = 0; j < shape.Length; j++) { if (axis == j) { continue; } if (shape[j] != firstShape[j]) { throw new IncorrectShapeException("all the input array dimensions except for the concatenation axis must match exactly."); } } } //prepare return shape firstShape[axis] = axisSize; var retShape = new Shape(firstShape); var dst = new NDArray(retType, retShape); var accessorDst = new Slice[retShape.NDim]; var accessorSrc = new Slice[retShape.NDim]; for (i = 0; i < accessorDst.Length; i++) { accessorSrc[i] = accessorDst[i] = Slice.All; } accessorSrc[axis] = Slice.Index(0); accessorDst[axis] = Slice.Index(0); foreach (var src in arrays) { var len = src.shape[axis]; for (i = 0; i < len; i++) { var writeTo = dst[accessorDst]; var writeFrom = src[accessorSrc]; MultiIterator.Assign(writeTo.Storage, writeFrom.Storage); accessorSrc[axis]++; accessorDst[axis]++; //increment every step } accessorSrc[axis] = Slice.Index(0); //reset src } return(dst); }
/// <summary> /// Get n-th dimension data /// </summary> /// <param name="indices">indexes</param> /// <returns>NDArray</returns> private NDArray GetData(params int[] indices) { if (indices.Length == 0) { return(this); } if (Storage.SupportsSpan) { Shape s1 = shape.Skip(indices.Length).ToArray(); var nd = new NDArray(dtype, s1); //nd.Storage.Slice = new Slice($"{}"); switch (Type.GetTypeCode(dtype)) { case TypeCode.Boolean: nd.Array = Storage.GetSpanData <bool>(slice, indices).ToArray(); break; case TypeCode.Byte: nd.Array = Storage.GetSpanData <byte>(slice, indices).ToArray(); break; case TypeCode.Int16: nd.Array = Storage.GetSpanData <short>(slice, indices).ToArray(); break; case TypeCode.Int32: nd.Array = Storage.GetSpanData <int>(slice, indices).ToArray(); break; case TypeCode.Int64: nd.Array = Storage.GetSpanData <long>(slice, indices).ToArray(); break; case TypeCode.Single: nd.Array = Storage.GetSpanData <float>(slice, indices).ToArray(); break; case TypeCode.Double: nd.Array = Storage.GetSpanData <double>(slice, indices).ToArray(); break; case TypeCode.Decimal: nd.Array = Storage.GetSpanData <decimal>(slice, indices).ToArray(); break; case TypeCode.String: nd.Array = Storage.GetSpanData <string>(slice, indices).ToArray(); break; default: return(Storage.GetSpanData <NDArray>(slice, indices).ToArray()[0]); } return(nd); } if (indices.Length < Storage.Shape.NDim) { // a slice was requested return(this[indices.Select(i => Slice.Index(i)).ToArray()]); } else if (indices.Length == Storage.Shape.NDim) { // a scalar was indexed var nd = new NDArray(this.dtype, new Shape()); switch (Type.GetTypeCode(dtype)) { case TypeCode.Boolean: nd.Array = new [] { Storage.GetData <bool>(indices) }; break; case TypeCode.Byte: nd.Array = new[] { Storage.GetData <byte>(indices) }; break; case TypeCode.Int16: nd.Array = new[] { Storage.GetData <short>(indices) }; break; case TypeCode.Int32: nd.Array = new[] { Storage.GetData <int>(indices) }; break; case TypeCode.Int64: nd.Array = new[] { Storage.GetData <long>(indices) }; break; case TypeCode.Single: nd.Array = new[] { Storage.GetData <float>(indices) }; break; case TypeCode.Double: nd.Array = new[] { Storage.GetData <double>(indices) }; break; case TypeCode.Decimal: nd.Array = new[] { Storage.GetData <decimal>(indices) }; break; case TypeCode.String: nd.Array = new[] { Storage.GetData <string>(indices) }; break; default: return(Storage.GetData <NDArray>(indices)); } return(nd); } else { throw new ArgumentException("Too many index dimensions for shape " + Storage.Shape); } }