001/*-
002 * Copyright (c) 2014, 2016 Diamond Light Source Ltd.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package org.eclipse.january.dataset;
011
012import java.util.Arrays;
013
014/**     
015 * The {@code SliceND} class represents a slice through all dimensions of a multi-dimensional {@link org.eclipse.january.dataset.Dataset}.<br><br>
016 * A slice comprises a starting position array, a stopping position array (not included) and a stepping size array.<br>
017 * If a maximum shape is specified, slicing past the original shape is supported for positive
018 * steps otherwise it is ignored. With unlimited dimensions, extending past the original shape is only
019 * allowed if the stopping value is given.
020 */
021public class SliceND {
022        private int[] lstart;
023        private int[] lstop;
024        private int[] lstep;
025        private transient int[] lshape; // latest shape
026        private int[] oshape; // source or original shape
027        private int[] mshape; // max shape
028
029        private boolean expanded;
030
031        /**
032         * Construct a nD Slice for whole of shape.
033         * 
034         * @param shape
035         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
036         */
037        public SliceND(final int[] shape) {
038                final int rank = shape.length;
039                lstart = new int[rank];
040                lstop = shape.clone();
041                lstep = new int[rank];
042                Arrays.fill(lstep, 1);
043                lshape = shape.clone();
044                oshape = shape.clone();
045                mshape = oshape;
046                expanded = false;
047        }
048
049        /**
050         * Construct a nD Slice from an array of 1D slices.
051         * 
052         * @param shape
053         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
054         * @param slice
055         *            Slice for each dimension of ND slice
056         */
057        public SliceND(final int[] shape, Slice... slice) {
058                this(shape, null, slice);
059        }
060
061        /**
062         * Construct a nD Slice from an array of 1D slices, if the maxShape is
063         * {@code null}, it will be set to the maximum shape of the nD Slice.
064         * 
065         * @param shape
066         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
067         * @param maxShape,
068         *            may be {@code null}
069         * @param slice
070         *            Slice for each dimension of ND slice
071         */
072        public SliceND(final int[] shape, final int[] maxShape, Slice... slice) {
073                this(shape);
074
075                if (maxShape != null) {
076                        initMaxShape(maxShape);
077                }
078
079                if (slice != null) {
080                        final int length = slice.length;
081                        final int rank = shape.length;
082                        if (length > rank) {
083                                throw new IllegalArgumentException("More slices have been specified than rank of shape");
084                        }
085                        for (int i = 0; i < length; i++) {
086                                Slice s = slice[i];
087                                if (s != null) {
088                                        setSlice(i, s);
089                                }
090                        }
091                }
092        }
093
094        private void initMaxShape(int[] maxShape) {
095                final int rank = oshape.length;
096                if (maxShape.length != rank) {
097                        throw new IllegalArgumentException("Maximum shape must have same rank as shape");
098                }
099                mshape = maxShape.clone();
100                for (int i = 0; i < rank; i++) {
101                        int m = mshape[i];
102                        if (m != ILazyWriteableDataset.UNLIMITED && m < oshape[i]) {
103                                throw new IllegalArgumentException("Maximum shape must be greater than or equal to shape");
104                        }
105                }
106        }
107
108        /**
109         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
110         * will be set to the maximum shape of the nD Slice, the start will be set
111         * to 0, stop is by default equal to the entire size of the set, step is
112         * defaultly set to 1.
113         * 
114         * @param shape
115         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
116         * @param start
117         *            Array of starts points, may be {@code null}
118         * @param stop
119         *            Array of stops points, may be {@code null}
120         * @param step
121         *            Array of steps, may be {@code null}
122         */
123        public SliceND(final int[] shape, final int[] start, final int[] stop, final int[] step) {
124                this(shape, null, start, stop, step);
125        }
126
127        /**
128         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
129         * will be set to the maximum shape of the nD Slice, the start will be set
130         * to 0, stop is by default equal to the entire size of the set, step is
131         * defaultly set to 1.
132         * 
133         * @param shape
134         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
135         * @param maxShape
136         *            Array of maximals shapes, may be {@code null}
137         * @param start
138         *            Array of starts points, may be {@code null}
139         * @param stop
140         *            Array of stops points, may be {@code null}
141         * @param step
142         *            Array of steps, may be {@code null}
143         */
144        public SliceND(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
145                // number of steps, or new shape, taken in each dimension is
146                // shape = (stop - start + step - 1) / step if step > 0
147                // (stop - start + step + 1) / step if step < 0
148                //
149                // thus the final index in each dimension is
150                // start + (shape-1)*step
151
152                int rank = shape.length;
153
154                if (start == null) {
155                        lstart = new int[rank];
156                } else {
157                        lstart = start.clone();
158                }
159                if (stop == null) {
160                        lstop = new int[rank];
161                } else {
162                        lstop = stop.clone();
163                }
164                if (step == null) {
165                        lstep = new int[rank];
166                        Arrays.fill(lstep, 1);
167                } else {
168                        lstep = step.clone();
169                }
170
171                if (lstart.length != rank || lstop.length != rank || lstep.length != rank) {
172                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
173                                        + lstart.length + ", stop=" + lstop.length + ", step=" + lstep.length + ", and it needs " + rank);
174                }
175
176                lshape = new int[rank];
177                oshape = shape.clone();
178                if (maxShape == null) {
179                        mshape = oshape;
180                } else {
181                        initMaxShape(maxShape);
182                }
183
184                for (int i = 0; i < rank; i++) {
185                        internalSetSlice(i, start == null ? null : lstart[i], stop == null ? null : lstop[i], lstep[i]);
186                }
187        }
188
189        /**
190         * Set slice for given dimension, if the start is {@code null} it will be
191         * set to 0, stop is by default equal to the entire size of the set.
192         * 
193         * @param i
194         *            dimension
195         * @param start
196         *            Start point, may be {@code null} to imply start of dimension
197         * @param stop
198         *            Stop point, may be {@code null} to imply end of dimension
199         * @param step
200         *            Slice step
201         */
202        public void setSlice(int i, Integer start, Integer stop, int step) {
203                internalSetSlice(i, start, stop, step);
204        }
205
206        /**
207         * Set slice for given dimension, if the start is {@code null} it will be
208         * set to 0, stop is by default equal to the entire size of the set.
209         * 
210         * @param i
211         *            dimension
212         * @param start
213         *            Start point, may be {@code null} to imply start of dimension
214         * @param stop
215         *            Stop point, may be {@code null} to imply end of dimension
216         * @param step
217         *            Slice step
218         */
219        public void setSlice(int i, int start, int stop, int step) {
220                internalSetSlice(i, start, stop, step);
221        }
222
223        /**
224         * Set slice for given dimension.
225         * 
226         * @param i
227         *            Dimension
228         * @param slice
229         *            Slice with wanted properties to set
230         * @since 2.0
231         */
232        public void setSlice(int i, Slice slice) {
233                internalSetSlice(i, slice.getStart(), slice.getStop(), slice.getStep());
234        }
235
236        /**
237         * Set slice for given dimension, if the start is {@code null} it will be
238         * set to 0, stop is by default equal to the entire size of the set.
239         * 
240         * @param i
241         *            dimension
242         * @param start
243         *            Start point, may be {@code null} to imply start of dimension
244         * @param stop
245         *            Stop point, may be {@code null} to imply end of dimension
246         * @param step
247         *            Slice step
248         */
249        private void internalSetSlice(int i, Integer start, Integer stop, int step) {
250                if (step == 0) {
251                        throw new IllegalArgumentException("Step size must not be zero");
252                }
253                i = ShapeUtils.checkAxis(oshape.length, i);
254                final int s = oshape[i];
255                final int m = mshape[i];
256
257                if (start == null) {
258                        start = step > 0 ? 0 : s - 1;
259                } else if (start < 0) {
260                        start += s;
261                }
262                if (step > 0) {
263                        if (start < 0) {
264                                start = 0;
265                        } else if (start > s) {
266                                if (m == s) {
267                                        start = s;
268                                } else if (m != ILazyWriteableDataset.UNLIMITED && start > m) {
269                                        start = m;
270                                }
271                        }
272
273                        if (stop == null) {
274                                if (start >= s && m == ILazyWriteableDataset.UNLIMITED) {
275                                        throw new IllegalArgumentException(
276                                                        "To extend past current dimension in unlimited case, a stop value must be specified");
277                                }
278                                stop = s;
279                        } else if (stop < 0) {
280                                stop += s;
281                        }
282                        if (stop < 0) {
283                                stop = 0;
284                        } else if (stop > s) {
285                                if (m == s) {
286                                        stop = s;
287                                } else if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
288                                        stop = m;
289                                }
290                        }
291
292                        if (start >= stop) {
293                                if (start < s || m == s) {
294                                        lstop[i] = start;
295                                } else { // override end
296                                        stop = start + step;
297                                        if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
298                                                stop = m;
299                                        }
300                                        lstop[i] = stop;
301                                }
302                        } else {
303                                lstop[i] = stop;
304                        }
305
306                        if (lstop[i] > s) {
307                                oshape[i] = lstop[i];
308                                expanded = true;
309                        }
310                } else {
311                        if (start < 0) {
312                                start = -1;
313                        } else if (start >= s) {
314                                start = s - 1;
315                        }
316
317                        if (stop == null) {
318                                stop = -1;
319                        } else if (stop < 0) {
320                                stop += s;
321                        }
322                        if (stop < -1) {
323                                stop = -1;
324                        } else if (stop >= s) {
325                                stop = s - 1;
326                        }
327                        if (stop >= start) {
328                                lstop[i] = start;
329                        } else {
330                                lstop[i] = stop;
331                        }
332                }
333
334                stop = lstop[i];
335                if (start == stop) {
336                        lshape[i] = 0;
337                } else if (step > 0) {
338                        lshape[i] = Math.max(0, (stop - start - 1) / step + 1);
339                } else {
340                        lshape[i] = Math.max(0, (stop - start + 1) / step + 1);
341                }
342                lstart[i] = start;
343                lstep[i] = step;
344        }
345
346        /**
347         * Returns an array of shapes of the source Dataset (this can change for
348         * dynamic Datasets).
349         * 
350         * @return shape of source Dataset
351         */
352        public int[] getSourceShape() {
353                return oshape;
354        }
355
356        /**
357         * Returns an array of maximals shapes
358         * 
359         * @return maximum shape
360         */
361        public int[] getMaxShape() {
362                return mshape;
363        }
364
365        /**
366         * Returns {@code true} if the slice makes shape larger, else {@code false}.
367         * 
368         * @return {@code true} if slice makes shape larger, {@code false} in the
369         *         other case
370         */
371        public boolean isExpanded() {
372                return expanded;
373        }
374
375        /**
376         * Returns an array of resulting shapes (this can change if the start, stop,
377         * step arrays are changed).
378         * 
379         * @return resulting shape
380         */
381        public int[] getShape() {
382                return lshape;
383        }
384
385        /**
386         * Returns an array of the starts values.
387         * 
388         * @return start values
389         */
390        public int[] getStart() {
391                return lstart;
392        }
393
394        /**
395         * Returns an array of stops values.
396         * <p>
397         * Note : stop values are clamped to -1 for <b>negative</b> steps
398         * </p>
399         * 
400         * @return stop values
401         */
402        public int[] getStop() {
403                return lstop;
404        }
405
406        /**
407         * Returns an array of the steps values.
408         * 
409         * @return step values
410         */
411        public int[] getStep() {
412                return lstep;
413        }
414
415        /**
416         * Returns {@code true} if all of originals shapes are covered by positive
417         * steps slices, else {@code false}.
418         * 
419         * @return {@code true} if all of originals shapes is covered by this slice
420         *         with positive steps, {@code false} in the other case.
421         */
422        public boolean isAll() {
423                if (expanded) {
424                        return false;
425                }
426
427                boolean allData = Arrays.equals(oshape, getShape());
428                if (allData) {
429                        for (int i = 0; i < lshape.length; i++) {
430                                if (lstep[i] < 0) {
431                                        allData = false;
432                                        break;
433                                }
434                        }
435                }
436                return allData;
437        }
438
439        /**
440         * Flips the slice direction in given dimension, this means that slice
441         * begins at previous end point, steps in the opposite direction, and
442         * finishes at the previous start point.
443         * 
444         * @param i
445         *            dimension to flip
446         */
447        public SliceND flip(int i) {
448                i = ShapeUtils.checkAxis(lshape.length, i);
449
450                int beg = lstart[i];
451                int end = lstop[i];
452                int step = lstep[i];
453                int del = lstep[i] > 0 ? 1 : -1;
454
455                int num = (end - beg - del) / step + 1; // number of steps
456                lstart[i] = beg + (num - 1) * step;
457                lstop[i] = Math.max(beg - step, -1);
458                lstep[i] = -step;
459
460                return this;
461        }
462
463        /**
464         * Flips slices directions in all dimensions, this means that all slices are
465         * beginning at previous end point, steps are in the opposite direction, and
466         * finishes are at the previous start point.
467         */
468        public SliceND flip() {
469                int orank = lshape.length;
470                for (int i = 0; i < orank; i++) {
471                        flip(i);
472                }
473
474                return this;
475        }
476
477        /**
478         * Converts to a slice array all the Slices of the SliceND.
479         * 
480         * @return a Slice array
481         */
482        public Slice[] convertToSlice() {
483                int orank = lshape.length;
484
485                Slice[] slice = new Slice[orank];
486
487                for (int j = 0; j < orank; j++) {
488                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
489                }
490
491                return slice;
492        }
493
494        /**
495         * Creates a deep copy of the SliceND.
496         * 
497         * @return New SliceND with the current SliceND properties
498         */
499        @Override
500        public SliceND clone() {
501                SliceND c = new SliceND(oshape);
502                for (int i = 0; i < lshape.length; i++) {
503                        c.lstart[i] = lstart[i];
504                        c.lstop[i] = lstop[i];
505                        c.lstep[i] = lstep[i];
506                        c.lshape[i] = lshape[i];
507                }
508                c.expanded = expanded;
509                return c;
510        }
511
512        /**
513         * Returns a string construction of the sliceND with the python form.
514         * 
515         * @return Constructed String of all Slices
516         */
517        @Override
518        public String toString() {
519                final int rank = lshape.length;
520                if (rank == 0) {
521                        return "";
522                }
523                StringBuilder s = new StringBuilder();
524                for (int i = 0; i < rank; i++) {
525                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
526                        s.append(',');
527                }
528
529                return s.substring(0, s.length() - 1);
530        }
531
532        /**
533         * Creats SliceND from dataset.
534         * 
535         * @param data
536         *            ILazyDataset to treat
537         * @param start
538         *            Array of starts indexes
539         * @param stop
540         *            Array of stops indexes
541         * @return Constructed SliceND
542         */
543        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
544                return createSlice(data, start, stop, null);
545        }
546
547        /**
548         * Creating SliceND from dataset.
549         * 
550         * @param data
551         *            ILazyDataset to treat
552         * @param start
553         *            Array of starts indexes
554         * @param stop
555         *            Array of stops indexes
556         * @param step
557         *            Array of steps
558         * @return Constructed SliceND
559         */
560        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
561                if (data instanceof IDynamicDataset) {
562                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
563                }
564                return new SliceND(data.getShape(), start, stop, step);
565        }
566}