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                lshape[i] = calcLength(start, stop, step);
336                lstart[i] = start;
337                lstep[i] = step;
338        }
339
340        private static int calcLength(int start, int stop, int step) {
341                int l;
342                if (start == stop) {
343                        l = 0;
344                } else if (step > 0) {
345                        l = Math.max(0, (stop - start - 1) / step + 1);
346                } else {
347                        l = Math.max(0, (stop - start + 1) / step + 1);
348                }
349                return l;
350        }
351
352        /**
353         * Returns an array of shapes of the source Dataset (this can change for
354         * dynamic Datasets).
355         * 
356         * @return shape of source Dataset
357         */
358        public int[] getSourceShape() {
359                return oshape;
360        }
361
362        /**
363         * Returns an array of maximals shapes
364         * 
365         * @return maximum shape
366         */
367        public int[] getMaxShape() {
368                return mshape;
369        }
370
371        /**
372         * Returns {@code true} if the slice makes shape larger, else {@code false}.
373         * 
374         * @return {@code true} if slice makes shape larger, {@code false} in the
375         *         other case
376         */
377        public boolean isExpanded() {
378                return expanded;
379        }
380
381        /**
382         * Returns an array of resulting shapes (this can change if the start, stop,
383         * step arrays are changed).
384         * 
385         * @return resulting shape
386         */
387        public int[] getShape() {
388                return lshape;
389        }
390
391        /**
392         * Returns an array of the starts values.
393         * 
394         * @return start values
395         */
396        public int[] getStart() {
397                return lstart;
398        }
399
400        /**
401         * Returns an array of stops values.
402         * <p>
403         * Note : stop values are clamped to -1 for <b>negative</b> steps
404         * </p>
405         * 
406         * @return stop values
407         */
408        public int[] getStop() {
409                return lstop;
410        }
411
412        /**
413         * Returns an array of the steps values.
414         * 
415         * @return step values
416         */
417        public int[] getStep() {
418                return lstep;
419        }
420
421        /**
422         * Returns {@code true} if all of originals shapes are covered by positive
423         * steps slices, else {@code false}.
424         * 
425         * @return {@code true} if all of originals shapes is covered by this slice
426         *         with positive steps, {@code false} in the other case.
427         */
428        public boolean isAll() {
429                if (expanded) {
430                        return false;
431                }
432
433                boolean allData = Arrays.equals(oshape, getShape());
434                if (allData) {
435                        for (int i = 0; i < lshape.length; i++) {
436                                if (lstep[i] < 0) {
437                                        allData = false;
438                                        break;
439                                }
440                        }
441                }
442                return allData;
443        }
444
445        /**
446         * Flips the slice direction in given dimension, this means that slice
447         * begins at previous end point, steps in the opposite direction, and
448         * finishes at the previous start point.
449         * 
450         * @param i
451         *            dimension to flip
452         */
453        public SliceND flip(int i) {
454                i = ShapeUtils.checkAxis(lshape.length, i);
455
456                int beg = lstart[i];
457                int end = lstop[i];
458                int step = lstep[i];
459                int del = lstep[i] > 0 ? 1 : -1;
460
461                int num = (end - beg - del) / step + 1; // number of steps
462                lstart[i] = beg + (num - 1) * step;
463                lstop[i] = Math.max(beg - step, -1);
464                lstep[i] = -step;
465
466                return this;
467        }
468
469        /**
470         * Flips slices directions in all dimensions, this means that all slices are
471         * beginning at previous end point, steps are in the opposite direction, and
472         * finishes are at the previous start point.
473         */
474        public SliceND flip() {
475                int orank = lshape.length;
476                for (int i = 0; i < orank; i++) {
477                        flip(i);
478                }
479
480                return this;
481        }
482
483        /**
484         * Converts to a slice array all the Slices of the SliceND.
485         * 
486         * @return a Slice array
487         */
488        public Slice[] convertToSlice() {
489                int orank = lshape.length;
490
491                Slice[] slice = new Slice[orank];
492
493                for (int j = 0; j < orank; j++) {
494                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
495                }
496
497                return slice;
498        }
499
500        /**
501         * Creates a deep copy of the SliceND.
502         * 
503         * @return New SliceND with the current SliceND properties
504         */
505        @Override
506        public SliceND clone() {
507                SliceND c = new SliceND(oshape);
508                for (int i = 0; i < lshape.length; i++) {
509                        c.lstart[i] = lstart[i];
510                        c.lstop[i] = lstop[i];
511                        c.lstep[i] = lstep[i];
512                        c.lshape[i] = lshape[i];
513                }
514                c.expanded = expanded;
515                return c;
516        }
517
518        /**
519         * Returns a string construction of the sliceND with the python form.
520         * 
521         * @return Constructed String of all Slices
522         */
523        @Override
524        public String toString() {
525                final int rank = lshape.length;
526                if (rank == 0) {
527                        return "";
528                }
529                StringBuilder s = new StringBuilder();
530                for (int i = 0; i < rank; i++) {
531                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
532                        s.append(',');
533                }
534
535                return s.substring(0, s.length() - 1);
536        }
537
538        /**
539         * Creats SliceND from dataset.
540         * 
541         * @param data
542         *            ILazyDataset to treat
543         * @param start
544         *            Array of starts indexes
545         * @param stop
546         *            Array of stops indexes
547         * @return Constructed SliceND
548         */
549        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
550                return createSlice(data, start, stop, null);
551        }
552
553        /**
554         * Creating SliceND from dataset.
555         * 
556         * @param data
557         *            ILazyDataset to treat
558         * @param start
559         *            Array of starts indexes
560         * @param stop
561         *            Array of stops indexes
562         * @param step
563         *            Array of steps
564         * @return Constructed SliceND
565         */
566        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
567                if (data instanceof IDynamicDataset) {
568                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
569                }
570                return new SliceND(data.getShape(), start, stop, step);
571        }
572
573        /**
574         * Create SliceND without sanity checks on start, stop and step
575         * @param shape
576         * @param maxShape
577         * @param start
578         * @param stop
579         * @param step
580         * @return slice
581         */
582        static SliceND createSlice(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
583                if (shape == null) {
584                        throw new IllegalArgumentException("Shape must not be null");
585                }
586                int rank = shape.length;
587
588                if (maxShape != null && maxShape.length != rank) {
589                        throw new IllegalArgumentException("Max shape must have same rank as shape");
590                }
591                if (start.length != rank || stop.length != rank || step.length != rank) {
592                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
593                                        + start.length + ", stop=" + stop.length + ", step=" + step.length + ", and it needs " + rank);
594                }
595
596                SliceND s = new SliceND(shape);
597                if (maxShape != null) {
598                        s.initMaxShape(maxShape);
599                }
600                s.lstart = start;
601                s.lstop  = stop;
602                s.lstep  = step;
603
604                for (int i = 0; i < rank; i++) {
605                        int e = stop[i];
606                        s.lshape[i] = calcLength(start[i], e, step[i]);
607                        if (e > shape[i]) {
608                                s.oshape[i] = e;
609                                s.expanded = true;
610                        }
611                }
612                return s;
613        }
614
615}