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 * Class to represent a slice through all dimensions of a multi-dimensional dataset. A slice
016 * comprises a starting position array, a stopping position array (not included) and a stepping size array.
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 ND slice for whole of shape
033         * @param shape
034         */
035        public SliceND(final int[] shape) {
036                final int rank = shape.length;
037                lstart = new int[rank];
038                lstop  = shape.clone();
039                lstep  = new int[rank];
040                Arrays.fill(lstep, 1);
041                lshape = shape.clone();
042                oshape = shape.clone();
043                mshape = oshape;
044                expanded = false;
045        }
046
047        /**
048         * Construct ND slice from an array of 1D slices
049         * @param shape
050         * @param slice
051         */
052        public SliceND(final int[] shape, Slice... slice) {
053                this(shape, null, slice);
054        }
055
056        /**
057         * Construct ND slice from an array of 1D slices
058         * @param shape
059         * @param maxShape can be null
060         * @param slice
061         */
062        public SliceND(final int[] shape, final int[] maxShape, Slice... slice) {
063                this(shape);
064
065                if (maxShape != null) {
066                        initMaxShape(maxShape);
067                }
068
069                if (slice != null) {
070                        final int length = slice.length;
071                        final int rank = shape.length;
072                        if (length > rank) {
073                                throw new IllegalArgumentException("More slices have been specified than rank of shape");
074                        }
075                        for (int i = 0; i < length; i++) {
076                                Slice s = slice[i];
077                                if (s != null) {
078                                        setSlice(i, s);
079                                }
080                        }
081                }
082        }
083
084        private void initMaxShape(int[] maxShape) {
085                final int rank = oshape.length;
086                if (maxShape.length != rank) {
087                        throw new IllegalArgumentException("Maximum shape must have same rank as shape");
088                }
089                mshape = maxShape.clone();
090                for (int i = 0; i < rank; i++) {
091                        int m = mshape[i];
092                        if (m != ILazyWriteableDataset.UNLIMITED && m < oshape[i]) {
093                                throw new IllegalArgumentException("Maximum shape must be greater than or equal to shape");
094                        }
095                }
096        }
097
098        /**
099         * Construct ND slice parameters
100         * 
101         * @param shape
102         * @param start
103         *            can be null
104         * @param stop
105         *            can be null
106         * @param step
107         *            can be null
108         */
109        public SliceND(final int[] shape, final int[] start, final int[] stop, final int[] step) {
110                this(shape, null, start, stop, step);
111        }
112
113        /**
114         * Construct ND slice parameters
115         * 
116         * @param shape
117         * @param maxShape can be null
118         * @param start
119         *            can be null
120         * @param stop
121         *            can be null
122         * @param step
123         *            can be null
124         */
125        public SliceND(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
126                // number of steps, or new shape, taken in each dimension is
127                // shape = (stop - start + step - 1) / step if step > 0
128                // (stop - start + step + 1) / step if step < 0
129                //
130                // thus the final index in each dimension is
131                // start + (shape-1)*step
132
133                int rank = shape.length;
134
135                if (start == null) {
136                        lstart = new int[rank];
137                } else {
138                        lstart = start.clone();
139                }
140                if (stop == null) {
141                        lstop = new int[rank];
142                } else {
143                        lstop = stop.clone();
144                }
145                if (step == null) {
146                        lstep = new int[rank];
147                        Arrays.fill(lstep, 1);
148                } else {
149                        lstep = step.clone();
150                }
151
152                if (lstart.length != rank || lstop.length != rank || lstep.length != rank) {
153                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
154                                        + lstart.length + ", stop=" + lstop.length + ", step=" + lstep.length + ", and it needs " + rank);
155                }
156
157                lshape = new int[rank];
158                oshape = shape.clone();
159                if (maxShape == null) {
160                        mshape = oshape;
161                } else {
162                        initMaxShape(maxShape);
163                }
164
165                for (int i = 0; i < rank; i++) {
166                        internalSetSlice(i, start == null ? null : lstart[i], stop == null ? null : lstop[i], lstep[i]);
167                }
168        }
169
170        /**
171         * Set slice for given dimension
172         * @param i dimension
173         * @param start can be null to imply start of dimension
174         * @param stop can be null to imply end of dimension
175         * @param step
176         */
177        public void setSlice(int i, Integer start, Integer stop, int step) {
178                internalSetSlice(i, start, stop, step);
179        }
180
181        /**
182         * Set slice for given dimension
183         * @param i dimension
184         * @param start
185         * @param stop
186         * @param step
187         */
188        public void setSlice(int i, int start, int stop, int step) {
189                internalSetSlice(i, start, stop, step);
190        }
191
192        /**
193         * Set slice for given dimension
194         * @param i dimension
195         * @param slice
196         * @since 2.0
197         */
198        public void setSlice(int i, Slice slice) {
199                internalSetSlice(i, slice.getStart(), slice.getStop(), slice.getStep());
200        }
201
202        /**
203         * Set slice for given dimension
204         * @param i dimension
205         * @param start
206         * @param stop
207         * @param step
208         */
209        private void internalSetSlice(int i, Integer start, Integer stop, int step) {
210                if (step == 0) {
211                        throw new IllegalArgumentException("Step size must not be zero");
212                }
213                final int s = oshape[i];
214                final int m = mshape[i];
215
216                if (start == null) {
217                        start = step > 0 ? 0 : s - 1;
218                } else if (start < 0) {
219                        start += s;
220                }
221                if (step > 0) {
222                        if (start < 0) {
223                                start = 0;
224                        } else if (start > s) {
225                                if (m == s) {
226                                        start = s;
227                                } else if (m != ILazyWriteableDataset.UNLIMITED && start > m) {
228                                        start = m;
229                                }
230                        }
231
232                        if (stop == null) {
233                                if (start >= s && m == ILazyWriteableDataset.UNLIMITED) {
234                                        throw new IllegalArgumentException("To extend past current dimension in unlimited case, a stop value must be specified");
235                                }
236                                stop = s;
237                        } else if (stop < 0) {
238                                stop += s;
239                        }
240                        if (stop < 0) {
241                                stop = 0;
242                        } else if (stop > s) {
243                                if (m == s) {
244                                        stop = s;
245                                } else if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
246                                        stop = m;
247                                }
248                        }
249
250                        if (start >= stop) {
251                                if (start < s || m == s) {
252                                        lstop[i] = start;
253                                } else { // override end
254                                        stop = start + step;
255                                        if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
256                                                stop = m;
257                                        }
258                                        lstop[i] = stop;
259                                }
260                        } else {
261                                lstop[i] = stop;
262                        }
263
264                        if (lstop[i] > s) {
265                                oshape[i] = lstop[i];
266                                expanded = true;
267                        }
268                } else {
269                        if (start < 0) {
270                                start = -1;
271                        } else if (start >= s) {
272                                start = s - 1;
273                        }
274
275                        if (stop == null) {
276                                stop = -1;
277                        } else if (stop < 0) {
278                                stop += s;
279                        }
280                        if (stop < -1) {
281                                stop = -1;
282                        } else if (stop >= s) {
283                                stop = s - 1;
284                        }
285                        if (stop >= start) {
286                                lstop[i] = start;
287                        } else {
288                                lstop[i] = stop;
289                        }
290                }
291
292                stop = lstop[i];
293                if (start == stop) {
294                        lshape[i] = 0;
295                } else if (step > 0) {
296                        lshape[i] = Math.max(0, (stop - start - 1) / step + 1);
297                } else {
298                        lshape[i] = Math.max(0, (stop - start + 1) / step + 1);
299                }
300                lstart[i] = start;
301                lstep[i] = step;
302        }
303
304        /**
305         * @return shape of source dataset (this can change for dynamic datasets)
306         */
307        public int[] getSourceShape() {
308                return oshape;
309        }
310
311        /**
312         * @return maximum shape
313         */
314        public int[] getMaxShape() {
315                return mshape;
316        }
317
318        /**
319         * @return true if slice makes shape larger
320         */
321        public boolean isExpanded() {
322                return expanded;
323        }
324
325        /**
326         * @return resulting shape (this can change if the start, stop, step arrays are changed)
327         */
328        public int[] getShape() {
329                return lshape;
330        }
331
332        /**
333         * @return start values
334         */
335        public int[] getStart() {
336                return lstart;
337        }
338
339        /**
340         * Note stop values are clamped to -1 for <b>negative</b> steps
341         * @return stop values
342         */
343        public int[] getStop() {
344                return lstop;
345        }
346
347        /**
348         * @return step values
349         */
350        public int[] getStep() {
351                return lstep;
352        }
353
354        /**
355         * @return true if all of original shape is covered by this slice with positive steps
356         */
357        public boolean isAll() {
358                if (expanded) {
359                        return false;
360                }
361
362                boolean allData = Arrays.equals(oshape, getShape());
363                if (allData) {
364                        for (int i = 0; i < lshape.length; i++) {
365                                if (lstep[i] < 0) {
366                                        allData = false;
367                                        break;
368                                }
369                        }
370                }
371                return allData;
372        }
373
374        /**
375         * Flip slice direction in given dimension so slice begins at previous end point,
376         * steps in the opposite direction, and finishes at the previous start point  
377         * @param i dimension to flip
378         */
379        public SliceND flip(int i) {
380                if (i < 0 || i >= lshape.length) {
381                        throw new IllegalArgumentException("Given dimension is less than zero or greater than last dimension");
382                }
383
384                int beg = lstart[i];
385                int end = lstop[i];
386                int step = lstep[i];
387                int del = lstep[i] > 0 ? 1 : -1;
388
389                int num = (end - beg - del) / step + 1; // number of steps
390                lstart[i] = beg + (num - 1) * step;
391                lstop[i] = Math.max(beg - step, -1);
392                lstep[i] = -step;
393
394                return this;
395        }
396
397        /**
398         * Flip slice direction in all dimensions so slice begins at previous end point,
399         * steps in the opposite direction, and finishes at the previous start point  
400         */
401        public SliceND flip() {
402                int orank = lshape.length;
403                for (int i = 0; i < orank; i++) {
404                        flip(i);
405                }
406
407                return this;
408        }
409
410        /**
411         * Convert to a slice array
412         * @return a slice array
413         */
414        public Slice[] convertToSlice() {
415                int orank = lshape.length;
416
417                Slice[] slice = new Slice[orank];
418
419                for (int j = 0; j < orank; j++) {
420                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
421                }
422
423                return slice;
424        }
425
426        @Override
427        public SliceND clone() {
428                SliceND c = new SliceND(oshape);
429                for (int i = 0; i < lshape.length; i++) {
430                        c.lstart[i] = lstart[i];
431                        c.lstop[i] = lstop[i];
432                        c.lstep[i] = lstep[i];
433                        c.lshape[i] = lshape[i];
434                }
435                c.expanded = expanded;
436                return c;
437        }
438
439        @Override
440        public String toString() {
441                final int rank = lshape.length;
442                if (rank == 0) {
443                        return "";
444                }
445                StringBuilder s = new StringBuilder();
446                for (int i = 0; i < rank; i++) {
447                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
448                        s.append(',');
449                }
450
451                return s.substring(0, s.length()-1);
452        }
453
454        /**
455         * Creating slice from dataset
456         * @param data
457         * @param start
458         * @param stop
459         * @return slice
460         */
461        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
462                return createSlice(data, start, stop, null);
463        }
464
465        /**
466         * Creating slice from dataset
467         * @param data
468         * @param start
469         * @param stop
470         * @param step
471         * @return slice
472         */
473        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
474                if (data instanceof IDynamicDataset) {
475                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
476                }
477                return new SliceND(data.getShape(), start, stop, step);
478        }
479}