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        private static int[] copy(int[] in) {
032                return in == null ? null : in.clone();
033        }
034
035        /**
036         * Construct a nD Slice for whole of shape.
037         * 
038         * @param shape
039         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
040         */
041        public SliceND(final int[] shape) {
042                final int rank = shape == null ? 0 : shape.length;
043                lstart = new int[rank];
044                lstop = copy(shape);
045                lstep = new int[rank];
046                Arrays.fill(lstep, 1);
047                lshape = copy(shape);
048                oshape = copy(shape);
049                mshape = oshape;
050                expanded = false;
051        }
052
053        /**
054         * Construct a nD Slice from an array of 1D slices.
055         * 
056         * @param shape
057         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
058         * @param slice
059         *            Slice for each dimension of ND slice
060         */
061        public SliceND(final int[] shape, Slice... slice) {
062                this(shape, null, slice);
063        }
064
065        /**
066         * Construct a nD Slice from an array of 1D slices, if the maxShape is
067         * {@code null}, it will be set to the maximum shape of the nD Slice.
068         * 
069         * @param shape
070         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
071         * @param maxShape,
072         *            may be {@code null}
073         * @param slice
074         *            Slice for each dimension of ND slice
075         */
076        public SliceND(final int[] shape, final int[] maxShape, Slice... slice) {
077                this(shape);
078
079                if (maxShape != null) {
080                        initMaxShape(maxShape);
081                }
082
083                if (slice != null) {
084                        final int length = slice.length;
085                        final int rank = shape.length;
086                        if (length > rank) {
087                                throw new IllegalArgumentException("More slices have been specified than rank of shape");
088                        }
089                        for (int i = 0; i < length; i++) {
090                                Slice s = slice[i];
091                                if (s != null) {
092                                        setSlice(i, s);
093                                }
094                        }
095                }
096        }
097
098        private void initMaxShape(int[] maxShape) {
099                final int rank = oshape.length;
100                if (maxShape.length != rank) {
101                        throw new IllegalArgumentException("Maximum shape must have same rank as shape");
102                }
103                mshape = maxShape.clone();
104                for (int i = 0; i < rank; i++) {
105                        int m = mshape[i];
106                        if (m != ILazyWriteableDataset.UNLIMITED && m < oshape[i]) {
107                                throw new IllegalArgumentException("Maximum shape must be greater than or equal to shape");
108                        }
109                }
110        }
111
112        /**
113         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
114         * will be set to the maximum shape of the nD Slice, the start will be set
115         * to 0, stop is by default equal to the entire size of the set, step is
116         * defaultly set to 1.
117         * 
118         * @param shape
119         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
120         * @param start
121         *            Array of starts points, may be {@code null}
122         * @param stop
123         *            Array of stops points, may be {@code null}
124         * @param step
125         *            Array of steps, may be {@code null}
126         */
127        public SliceND(final int[] shape, final int[] start, final int[] stop, final int[] step) {
128                this(shape, null, start, stop, step);
129        }
130
131        /**
132         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
133         * will be set to the maximum shape of the nD Slice, the start will be set
134         * to 0, stop is by default equal to the entire size of the set, step is
135         * defaultly set to 1.
136         * 
137         * @param shape
138         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
139         * @param maxShape
140         *            Array of maximals shapes, may be {@code null}
141         * @param start
142         *            Array of starts points, may be {@code null}
143         * @param stop
144         *            Array of stops points, may be {@code null}
145         * @param step
146         *            Array of steps, may be {@code null}
147         */
148        public SliceND(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
149                // number of steps, or new shape, taken in each dimension is
150                // shape = (stop - start + step - 1) / step if step > 0
151                // (stop - start + step + 1) / step if step < 0
152                //
153                // thus the final index in each dimension is
154                // start + (shape-1)*step
155                final int rank = shape == null ? 0 : shape.length;
156
157                if (start == null) {
158                        lstart = new int[rank];
159                } else {
160                        lstart = start.clone();
161                }
162                if (stop == null) {
163                        lstop = new int[rank];
164                } else {
165                        lstop = stop.clone();
166                }
167                if (step == null) {
168                        lstep = new int[rank];
169                        Arrays.fill(lstep, 1);
170                } else {
171                        lstep = step.clone();
172                }
173
174                if (lstart.length != rank || lstop.length != rank || lstep.length != rank) {
175                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
176                                        + lstart.length + ", stop=" + lstop.length + ", step=" + lstep.length + ", and it needs " + rank);
177                }
178
179                lshape = new int[rank];
180                oshape = copy(shape);
181                if (maxShape == null) {
182                        mshape = oshape;
183                } else {
184                        initMaxShape(maxShape);
185                }
186
187                for (int i = 0; i < rank; i++) {
188                        internalSetSlice(i, start == null ? null : lstart[i], stop == null ? null : lstop[i], lstep[i]);
189                }
190        }
191
192        /**
193         * Set slice for given dimension, if the start is {@code null} it will be
194         * set to 0, stop is by default equal to the entire size of the set.
195         * 
196         * @param i
197         *            dimension
198         * @param start
199         *            Start point, may be {@code null} to imply start of dimension
200         * @param stop
201         *            Stop point, may be {@code null} to imply end of dimension
202         * @param step
203         *            Slice step
204         */
205        public void setSlice(int i, Integer start, Integer stop, int step) {
206                internalSetSlice(i, start, stop, step);
207        }
208
209        /**
210         * Set slice for given dimension, if the start is {@code null} it will be
211         * set to 0, stop is by default equal to the entire size of the set.
212         * 
213         * @param i
214         *            dimension
215         * @param start
216         *            Start point, may be {@code null} to imply start of dimension
217         * @param stop
218         *            Stop point, may be {@code null} to imply end of dimension
219         * @param step
220         *            Slice step
221         */
222        public void setSlice(int i, int start, int stop, int step) {
223                internalSetSlice(i, start, stop, step);
224        }
225
226        /**
227         * Set slice for given dimension.
228         * 
229         * @param i
230         *            Dimension
231         * @param slice
232         *            Slice with wanted properties to set
233         * @since 2.0
234         */
235        public void setSlice(int i, Slice slice) {
236                internalSetSlice(i, slice.getStart(), slice.getStop(), slice.getStep());
237        }
238
239        /**
240         * Set slice for given dimension, if the start is {@code null} it will be
241         * set to 0, stop is by default equal to the entire size of the set.
242         * 
243         * @param i
244         *            dimension
245         * @param start
246         *            Start point, may be {@code null} to imply start of dimension
247         * @param stop
248         *            Stop point, may be {@code null} to imply end of dimension
249         * @param step
250         *            Slice step
251         */
252        private void internalSetSlice(int i, Integer start, Integer stop, int step) {
253                if (oshape == null) {
254                        throw new IllegalArgumentException("Cannot set slice on null dataset");
255                }
256                if (step == 0) {
257                        throw new IllegalArgumentException("Step size must not be zero");
258                }
259                i = ShapeUtils.checkAxis(oshape.length, i);
260                final int s = oshape[i];
261                final int m = mshape[i];
262
263                if (start == null) {
264                        start = step > 0 ? 0 : s - 1;
265                } else if (start < 0) {
266                        start += s;
267                }
268                if (step > 0) {
269                        if (start < 0) {
270                                start = 0;
271                        } else if (start > s) {
272                                if (m == s) {
273                                        start = s;
274                                } else if (m != ILazyWriteableDataset.UNLIMITED && start > m) {
275                                        start = m;
276                                }
277                        }
278
279                        if (stop == null) {
280                                if (start >= s && m == ILazyWriteableDataset.UNLIMITED) {
281                                        throw new IllegalArgumentException(
282                                                        "To extend past current dimension in unlimited case, a stop value must be specified");
283                                }
284                                stop = s;
285                        } else if (stop < 0) {
286                                stop += s;
287                        }
288                        if (stop < 0) {
289                                stop = 0;
290                        } else if (stop > s) {
291                                if (m == s) {
292                                        stop = s;
293                                } else if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
294                                        stop = m;
295                                }
296                        }
297
298                        if (start >= stop) {
299                                if (start < s || m == s) {
300                                        lstop[i] = start;
301                                } else { // override end
302                                        stop = start + step;
303                                        if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
304                                                stop = m;
305                                        }
306                                        lstop[i] = stop;
307                                }
308                        } else {
309                                lstop[i] = stop;
310                        }
311
312                        if (lstop[i] > s) {
313                                oshape[i] = lstop[i];
314                                expanded = true;
315                        }
316                } else {
317                        if (start < 0) {
318                                start = -1;
319                        } else if (start >= s) {
320                                start = s - 1;
321                        }
322
323                        if (stop == null) {
324                                stop = -1;
325                        } else if (stop < 0) {
326                                stop += s;
327                        }
328                        if (stop < -1) {
329                                stop = -1;
330                        } else if (stop >= s) {
331                                stop = s - 1;
332                        }
333                        if (stop >= start) {
334                                lstop[i] = start;
335                        } else {
336                                lstop[i] = stop;
337                        }
338                }
339
340                stop = lstop[i];
341                lshape[i] = calcLength(start, stop, step);
342                lstart[i] = start;
343                lstep[i] = step;
344        }
345
346        private static int calcLength(int start, int stop, int step) {
347                int l;
348                if (start == stop) {
349                        l = 0;
350                } else if (step > 0) {
351                        l = Math.max(0, (stop - start - 1) / step + 1);
352                } else {
353                        l = Math.max(0, (stop - start + 1) / step + 1);
354                }
355                return l;
356        }
357
358        /**
359         * Returns an array of shapes of the source Dataset (this can change for
360         * dynamic Datasets).
361         * 
362         * @return shape of source Dataset
363         */
364        public int[] getSourceShape() {
365                return oshape;
366        }
367
368        /**
369         * Returns an array of maximals shapes
370         * 
371         * @return maximum shape
372         */
373        public int[] getMaxShape() {
374                return mshape;
375        }
376
377        /**
378         * Returns {@code true} if the slice makes shape larger, else {@code false}.
379         * 
380         * @return {@code true} if slice makes shape larger, {@code false} in the
381         *         other case
382         */
383        public boolean isExpanded() {
384                return expanded;
385        }
386
387        /**
388         * Returns an array of resulting shapes (this can change if the start, stop,
389         * step arrays are changed).
390         * 
391         * @return resulting shape
392         */
393        public int[] getShape() {
394                return lshape;
395        }
396
397        /**
398         * Returns an array of the starts values.
399         * 
400         * @return start values
401         */
402        public int[] getStart() {
403                return lstart;
404        }
405
406        /**
407         * Returns an array of stops values.
408         * <p>
409         * Note : stop values are clamped to -1 for <b>negative</b> steps
410         * </p>
411         * 
412         * @return stop values
413         */
414        public int[] getStop() {
415                return lstop;
416        }
417
418        /**
419         * Returns an array of the steps values.
420         * 
421         * @return step values
422         */
423        public int[] getStep() {
424                return lstep;
425        }
426
427        /**
428         * Update source shape (for use with dynamic datasets)
429         * @param shape source shape
430         * @since 2.3
431         */
432        public void updateSourceShape(int... shape) {
433                if (shape != null) {
434                        if (oshape != null) {
435                                if (shape.length != oshape.length) {
436                                        throw new IllegalArgumentException("Updated source shape must have same rank");
437                                }
438                                for (int i = 0; i < shape.length; i++) {
439                                        int s = shape[i];
440                                        if ((s > 1 || s < lshape[i]) && !isSliceWithinShape(s, lstart[i], lstop[i], lstep[i])) {
441                                                throw new IllegalArgumentException("Updated source shape must be outside latest slice");
442                                        }
443                                }
444                                System.arraycopy(shape, 0, oshape, 0, shape.length);
445                        } else {
446                                throw new IllegalArgumentException("Cannot update null source shape with non-null");
447                        }
448                } else if (oshape != null) {
449                        throw new IllegalArgumentException("Cannot update non-null source shape with null");
450                }
451        }
452
453        /**
454         * Check slice configure
455         * @param l length of dimension
456         * @param b beginning
457         * @param e end
458         * @param d delta
459         * @return true if okay
460         */
461        static boolean isSliceWithinShape(int l, int b, int e, int d) {
462                if (l == 0) {
463                        return b == 0 && e == 0;
464                }
465                if (d > 0) {
466                        return b >= 0 && b <= e && b < l && e <= l;
467                }
468                return b >= 0 && b >= e && b < l && e >= -1;
469        }
470
471        /**
472         * Returns {@code true} if all of originals shapes are covered by positive
473         * steps slices, else {@code false}.
474         * 
475         * @return {@code true} if all of originals shapes is covered by this slice
476         *         with positive steps, {@code false} in the other case.
477         */
478        public boolean isAll() {
479                if (expanded) {
480                        return false;
481                }
482
483                if (oshape == null) {
484                        return true;
485                }
486
487                boolean allData = Arrays.equals(oshape, lshape);
488                if (allData) {
489                        for (int i = 0; i < lshape.length; i++) {
490                                if (lstep[i] < 0) {
491                                        allData = false;
492                                        break;
493                                }
494                        }
495                }
496                return allData;
497        }
498
499        /**
500         * Flips the slice direction in given dimension, this means that slice
501         * begins at previous end point, steps in the opposite direction, and
502         * finishes at the previous start point.
503         * 
504         * @param i
505         *            dimension to flip
506         * @return this
507         */
508        public SliceND flip(int i) {
509                if (lshape == null) {
510                        return this;
511                }
512
513                i = ShapeUtils.checkAxis(lshape.length, i);
514
515                int beg = lstart[i];
516                int end = lstop[i];
517                int step = lstep[i];
518                int del = lstep[i] > 0 ? 1 : -1;
519
520                int num = (end - beg - del) / step + 1; // number of steps
521                lstart[i] = beg + (num - 1) * step;
522                lstop[i] = Math.max(beg - step, -1);
523                lstep[i] = -step;
524
525                return this;
526        }
527
528        /**
529         * Flips slices directions in all dimensions, this means that all slices are
530         * beginning at previous end point, steps are in the opposite direction, and
531         * finishes are at the previous start point.
532         * @return this
533         */
534        public SliceND flip() {
535                if (lshape == null) {
536                        return this;
537                }
538                int orank = lshape.length;
539                for (int i = 0; i < orank; i++) {
540                        flip(i);
541                }
542
543                return this;
544        }
545
546        /**
547         * Converts to a slice array all the Slices of the SliceND.
548         * 
549         * @return a Slice array
550         */
551        public Slice[] convertToSlice() {
552                if (lshape == null) {
553                        return null;
554                }
555
556                int orank = lshape.length;
557
558                Slice[] slice = new Slice[orank];
559
560                for (int j = 0; j < orank; j++) {
561                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
562                }
563
564                return slice;
565        }
566
567        /**
568         * Creates a deep copy of the SliceND.
569         * 
570         * @return New SliceND with the current SliceND properties
571         */
572        @Override
573        public SliceND clone() {
574                SliceND c = new SliceND(oshape);
575                if (lshape != null) {
576                        for (int i = 0; i < lshape.length; i++) {
577                                c.lstart[i] = lstart[i];
578                                c.lstop[i] = lstop[i];
579                                c.lstep[i] = lstep[i];
580                                c.lshape[i] = lshape[i];
581                        }
582                }
583                c.expanded = expanded;
584                return c;
585        }
586
587        /**
588         * Returns a string construction of the sliceND with the python form.
589         * 
590         * @return Constructed String of all Slices
591         */
592        @Override
593        public String toString() {
594                final int rank = lshape.length;
595                if (rank == 0) {
596                        return "";
597                }
598                StringBuilder s = new StringBuilder();
599                for (int i = 0; i < rank; i++) {
600                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
601                        s.append(',');
602                }
603
604                return s.substring(0, s.length() - 1);
605        }
606
607        /**
608         * Creats SliceND from dataset.
609         * 
610         * @param data
611         *            ILazyDataset to treat
612         * @param start
613         *            Array of starts indexes
614         * @param stop
615         *            Array of stops indexes
616         * @return Constructed SliceND
617         */
618        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
619                return createSlice(data, start, stop, null);
620        }
621
622        /**
623         * Creating SliceND from dataset.
624         * 
625         * @param data
626         *            ILazyDataset to treat
627         * @param start
628         *            Array of starts indexes
629         * @param stop
630         *            Array of stops indexes
631         * @param step
632         *            Array of steps
633         * @return Constructed SliceND
634         */
635        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
636                if (data instanceof IDynamicDataset) {
637                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
638                }
639                return new SliceND(data.getShape(), start, stop, step);
640        }
641
642        /**
643         * Create SliceND without sanity checks on start, stop and step
644         * @param shape
645         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
646         * @param maxShape
647         *            Array of maximals shapes, may be {@code null}
648         * @param start
649         *            Array of starts points, may be {@code null}
650         * @param stop
651         *            Array of stops points, may be {@code null}
652         * @param step
653         *            Array of steps, may be {@code null}
654         * @return slice
655         */
656        static SliceND createSlice(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
657                if (shape == null) {
658                        throw new IllegalArgumentException("Shape must not be null");
659                }
660                int rank = shape.length;
661
662                if (maxShape != null && maxShape.length != rank) {
663                        throw new IllegalArgumentException("Max shape must have same rank as shape");
664                }
665                if (start.length != rank || stop.length != rank || step.length != rank) {
666                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
667                                        + start.length + ", stop=" + stop.length + ", step=" + step.length + ", and it needs " + rank);
668                }
669
670                SliceND s = new SliceND(shape);
671                if (maxShape != null) {
672                        s.initMaxShape(maxShape);
673                }
674                s.lstart = start;
675                s.lstop  = stop;
676                s.lstep  = step;
677
678                for (int i = 0; i < rank; i++) {
679                        int e = stop[i];
680                        s.lshape[i] = calcLength(start[i], e, step[i]);
681                        if (e > shape[i]) {
682                                s.oshape[i] = e;
683                                s.expanded = true;
684                        }
685                }
686                return s;
687        }
688
689}