001/*-
002 *******************************************************************************
003 * Copyright (c) 2011, 2016 Diamond Light Source Ltd.
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 * Contributors:
010 *    Peter Chang - initial API and implementation and/or initial documentation
011 *******************************************************************************/
012
013package org.eclipse.january.dataset;
014
015import java.io.IOException;
016import java.io.Serializable;
017import java.lang.annotation.Annotation;
018import java.lang.reflect.Field;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Map;
025
026import org.eclipse.january.DatasetException;
027import org.eclipse.january.IMonitor;
028import org.eclipse.january.io.ILazyLoader;
029import org.eclipse.january.metadata.MetadataFactory;
030import org.eclipse.january.metadata.MetadataType;
031import org.eclipse.january.metadata.OriginMetadata;
032import org.eclipse.january.metadata.Reshapeable;
033import org.eclipse.january.metadata.Sliceable;
034import org.eclipse.january.metadata.Transposable;
035
036public class LazyDataset extends LazyDatasetBase implements Serializable, Cloneable {
037        private static final long serialVersionUID = 2467865859867440242L;
038
039        protected Map<Class<? extends MetadataType>, List<MetadataType>> oMetadata = null;
040        protected int[] oShape; // original shape
041        protected long  size;   // number of items
042        private Class<? extends Dataset> clazz = null;
043        protected int   isize;  // number of elements per item
044
045        protected ILazyLoader loader;
046
047        // relative to loader
048        protected int[] begSlice = null; // slice begin
049        protected int[] delSlice = null; // slice delta
050        /**
051         * @since 2.2
052         */
053        protected int[] sShape   = null; // sliced shape
054
055        /**
056         * @since 2.2
057         */
058        protected int[] padding = null; // differences in shape from original (or sliced) shape
059        protected int[] map; // transposition map (same length as current shape)
060
061        /**
062         * Create a lazy dataset
063         * @param loader
064         * @param name
065         * @param elements
066         * @param clazz dataset interface
067         * @param shape
068         * @since 2.3
069         */
070        public LazyDataset(ILazyLoader loader, String name, int elements, Class<? extends Dataset> clazz, int... shape) {
071                this.loader = loader;
072                this.name = name;
073                this.isize = elements;
074                this.clazz = clazz;
075                this.shape = shape.clone();
076                this.oShape = this.shape;
077                try {
078                        size = ShapeUtils.calcLongSize(shape);
079                } catch (IllegalArgumentException e) {
080                        size = Long.MAX_VALUE; // this indicates that the entire dataset cannot be read in! 
081                }
082        }
083
084        /**
085         * Create a lazy dataset
086         * @param loader
087         * @param name
088         * @param clazz dataset interface
089         * @param shape
090         * @since 2.3
091         */
092        public LazyDataset(ILazyLoader loader, String name, Class<? extends Dataset> clazz, int... shape) {
093                this(loader, name, 1, clazz, shape);
094        }
095
096        /**
097         * Create a lazy dataset
098         * @param name
099         * @param dtype dataset type
100         * @param elements
101         * @param shape
102         * @param loader
103         * @deprecated Use {@link #LazyDataset(ILazyLoader, String, int, Class, int[])}
104         */
105        @Deprecated
106        public LazyDataset(String name, int dtype, int elements, int[] shape, ILazyLoader loader) {
107                this(loader, name, elements, DTypeUtils.getInterface(dtype), shape);
108        }
109
110        /**
111         * Create a lazy dataset
112         * @param name
113         * @param dtype dataset type
114         * @param shape
115         * @param loader
116         * @deprecated Use {@link #LazyDataset(ILazyLoader, String, int, Class, int[])}
117         */
118        @Deprecated
119        public LazyDataset(String name, int dtype, int[] shape, ILazyLoader loader) {
120                this(name, dtype, 1, shape, loader);
121        }
122
123        LazyDataset(LazyDataset other) {
124                name  = other.name;
125                shape = other.shape.clone();
126                metadata  = other.copyMetadata();
127                oMetadata = other.oMetadata;
128                oShape = other.oShape;
129                size   = other.size;
130                clazz  = other.clazz;
131                isize  = other.isize;
132                loader = other.loader;
133
134                begSlice = other.begSlice;
135                delSlice = other.delSlice;
136                sShape   = other.sShape;
137                padding  = other.padding;
138                map      = other.map;
139        }
140
141        /**
142         * Create a lazy dataset based on in-memory data (handy for testing)
143         * @param dataset
144         */
145        public static LazyDataset createLazyDataset(final Dataset dataset) {
146                return new LazyDataset(dataset.getName(), dataset.getDType(), dataset.getElementsPerItem(), dataset.getShapeRef(),
147                new ILazyLoader() {
148                        private static final long serialVersionUID = -6725268922780517523L;
149
150                        final Dataset d = dataset;
151                        @Override
152                        public boolean isFileReadable() {
153                                return true;
154                        }
155
156                        @Override
157                        public Dataset getDataset(IMonitor mon, SliceND slice) throws IOException {
158                                return d.getSlice(mon, slice);
159                        }
160                });
161        }
162
163        /**
164         * Can return -1 for unknown
165         */
166        @Override
167        public int getDType() {
168                return DTypeUtils.getDType(clazz);
169        }
170
171        /**
172         * @return dataset interface that supports element class and number of elements
173         * @since 2.3
174         */
175        public Class<? extends Dataset> getInterface() {
176                return clazz;
177        }
178
179        /**
180         * Can return -1 for unknown
181         */
182        @Override
183        public int getElementsPerItem() {
184                return isize;
185        }
186
187        @Override
188        public int getSize() {
189                return (int) size;
190        }
191
192        @Override
193        public String toString() {
194                StringBuilder out = new StringBuilder();
195
196                if (name != null && name.length() > 0) {
197                        out.append("Lazy dataset '");
198                        out.append(name);
199                        out.append("' has shape [");
200                } else {
201                        out.append("Lazy dataset shape is [");
202                }
203                int rank = shape == null ? 0 : shape.length;
204
205                if (rank > 0 && shape[0] >= 0) {
206                        out.append(shape[0]);
207                }
208                for (int i = 1; i < rank; i++) {
209                        out.append(", " + shape[i]);
210                }
211                out.append(']');
212
213                return out.toString();
214        }
215
216        @Override
217        public int hashCode() {
218                final int prime = 31;
219                int result = super.hashCode();
220                result = prime * result + Arrays.hashCode(oShape);
221                result = prime * result + (int) (size ^ (size >>> 32));
222                result = prime * result + clazz.hashCode();
223                result = prime * result + isize;
224                result = prime * result + ((loader == null) ? 0 : loader.hashCode());
225                result = prime * result + Arrays.hashCode(begSlice);
226                result = prime * result + Arrays.hashCode(delSlice);
227                result = prime * result + Arrays.hashCode(sShape);
228                result = prime * result + Arrays.hashCode(padding);
229                result = prime * result + Arrays.hashCode(map);
230                return result;
231        }
232
233        @Override
234        public boolean equals(Object obj) {
235                if (this == obj) {
236                        return true;
237                }
238                if (!super.equals(obj)) {
239                        return false;
240                }
241
242                LazyDataset other = (LazyDataset) obj;
243                if (!Arrays.equals(oShape, other.oShape)) {
244                        return false;
245                }
246                if (size != other.size) {
247                        return false;
248                }
249                if (!clazz.equals(other.clazz)) {
250                        return false;
251                }
252                if (isize != other.isize) {
253                        return false;
254                }
255
256                if (loader != other.loader) {
257                        return false;
258                }
259
260                if (!Arrays.equals(begSlice, other.begSlice)) {
261                        return false;
262                }
263                if (!Arrays.equals(delSlice, other.delSlice)) {
264                        return false;
265                }
266                if (!Arrays.equals(sShape, other.sShape)) {
267                        return false;
268                }
269                if (!Arrays.equals(padding, other.padding)) {
270                        return false;
271                }
272                if (!Arrays.equals(map, other.map)) {
273                        return false;
274                }
275
276                return true;
277        }
278
279        @Override
280        public LazyDataset clone() {
281                return new LazyDataset(this);
282        }
283
284        @Override
285        public void setShape(int... shape) {
286                setShapeInternal(shape.clone());
287        }
288
289        @Override
290        public LazyDataset squeezeEnds() {
291                setShapeInternal(ShapeUtils.squeezeShape(shape, true));
292                return this;
293        }
294
295        @Override
296        public Dataset getSlice(int[] start, int[] stop, int[] step) throws DatasetException {
297                return getSlice(null, start, stop, step);
298        }
299
300        @Override
301        public Dataset getSlice(Slice... slice) throws DatasetException {
302                if (slice == null || slice.length == 0) {
303                        return getSlice(null, new SliceND(shape));
304                }
305                return getSlice(null, new SliceND(shape, slice));
306        }
307
308        @Override
309        public Dataset getSlice(SliceND slice) throws DatasetException {
310                return getSlice(null, slice);
311        }
312
313        @Override
314        public Dataset getSlice(IMonitor monitor, Slice... slice) throws DatasetException {
315                if (slice == null || slice.length == 0) {
316                        return getSlice(monitor, new SliceND(shape));
317                }
318                return getSlice(monitor, new SliceND(shape, slice));
319        }
320
321        @Override
322        public LazyDataset getSliceView(Slice... slice) {
323                if (slice == null || slice.length == 0) {
324                        return getSliceView(new SliceND(shape));
325                }
326                return getSliceView(new SliceND(shape, slice));
327        }
328
329        /**
330         * @param nShape
331         */
332        private void setShapeInternal(int... nShape) {
333                // work out transposed (sliced) shape (instead of removing padding from current shape)
334                if (size != 0) {
335                        int[] pShape = calcTransposed(map, sShape == null ? oShape : sShape);
336                        padding = ShapeUtils.calcShapePadding(pShape, nShape);
337                }
338
339                if (metadata != null) {
340                        storeMetadata(metadata, Reshapeable.class);
341                        metadata = copyMetadata();
342                        reshapeMetadata(shape, nShape);
343                }
344                shape = nShape;
345        }
346
347        @Override
348        public LazyDataset getSliceView(int[] start, int[] stop, int[] step) {
349                return getSliceView(new SliceND(shape, start, stop, step));
350        }
351
352        @Override
353        public LazyDataset getSliceView(SliceND slice) {
354                LazyDataset view = clone();
355                if (slice.isAll()) {
356                        return view;
357                }
358
359                SliceND nslice = calcTrueSlice(slice);
360                if (nslice != null) {
361                        view.begSlice = nslice.getStart();
362                        view.delSlice = nslice.getStep();
363                        view.sShape = nslice.getShape();
364                }
365                view.shape = slice.getShape();
366                view.size = ShapeUtils.calcLongSize(view.shape);
367                view.storeMetadata(metadata, Sliceable.class);
368
369                view.sliceMetadata(true, slice);
370                return view;
371        }
372
373        @Override
374        public Dataset getSlice(IMonitor monitor, int[] start, int[] stop, int[] step) throws DatasetException {
375                return getSlice(monitor, new SliceND(shape, start, stop, step));
376        }
377
378        @Override
379        public Dataset getSlice(IMonitor monitor, SliceND slice) throws DatasetException {
380                if (loader != null && !loader.isFileReadable()) {
381                        return null;
382                }
383
384                SliceND nslice = calcTrueSlice(slice);
385
386                Dataset a;
387                if (nslice == null) {
388                        a = DatasetFactory.zeros(clazz, slice.getShape());
389                } else {
390                        try {
391                                a = DatasetUtils.convertToDataset(loader.getDataset(monitor, nslice));
392                        } catch (IOException e) {
393                                logger.error("Problem getting {}: {}", String.format("slice %s %s %s from %s", Arrays.toString(slice.getStart()), Arrays.toString(slice.getStop()),
394                                                                Arrays.toString(slice.getStep()), loader), e);
395                                throw new DatasetException(e);
396                        }
397                }
398                a.setName(name + AbstractDataset.BLOCK_OPEN + (nslice == null ? slice : nslice) + AbstractDataset.BLOCK_CLOSE);
399                if (metadata != null && a instanceof LazyDatasetBase) {
400                        LazyDatasetBase ba = (LazyDatasetBase) a;
401                        ba.metadata = copyMetadata();
402                        if (oMetadata != null) {
403                                ba.restoreMetadata(oMetadata);
404                        }
405                        // metadata axis may be larger than data
406                        if (nslice != null && (!nslice.isAll() || nslice.getMaxShape() != nslice.getShape())) {
407                                ba.sliceMetadata(true, nslice);
408                        }
409                }
410
411                if (nslice != null) {
412                        if (map != null) {
413                                a = a.getTransposedView(map);
414                        }
415                        if (padding != null) {
416                                a.setShape(slice.getShape());
417                        }
418                }
419                a.addMetadata(MetadataFactory.createMetadata(OriginMetadata.class, this, nslice == null ? slice.convertToSlice() : nslice.convertToSlice(), oShape, null, name));
420
421                return a;
422        }
423
424        @Override
425        public LazyDataset getTransposedView(final int... axes) {
426                LazyDataset view = clone();
427
428                int[] naxes = checkPermutatedAxes(shape, axes);
429                if (naxes == null) {
430                        return view;
431                }
432
433                view.shape = calcTransposed(naxes, shape);
434                if (view.size != 0 && padding != null) { // work out transpose by reverting effect of padding
435                        int or = oShape.length;
436                        int nr = shape.length;
437                        int j = 0; // naxes index
438                        int[] mShape = calcTransposed(map, sShape == null ? oShape : sShape); // pre-padded shape
439                        int m = 0; // shape index
440                        int e = -1; // index of unit dimension
441                        final List<Integer> uaxes = new LinkedList<>();
442                        for (int a : naxes) {
443                                uaxes.add(a);
444                        }
445                        List<Integer> oList = new ArrayList<>(); // dimensions left out by padding (in order)
446                        int np = padding.length;
447                        for (int i = 0; i < np; i++) {
448                                int p = padding[i];
449                                if (p > 0) { // remove added dimensions
450                                        for (int k = 0; k < p; k++, j++) {
451                                                uaxes.remove((Integer) j);
452                                        }
453                                } else if (p == 0) { // leave alone
454                                        if (mShape[m] == 1) { // bump up last unit dimension index
455                                                e = m;
456                                        }
457                                        j++;
458                                        m++;
459                                } else { // add omitted dimensions to list
460                                        p = -p;
461                                        for (int k = 0; k < p; k++) {
462                                                e = find(mShape, 1, e + 1);
463                                                oList.add(e);
464                                        }
465                                }
466                        }
467                        
468                        int[] omitted = new int[oList.size()];
469                        j = 0;
470                        for (Integer o : oList) {
471                                omitted[j++] = o;
472                        }
473                        int[] used = new int[or - omitted.length]; // all dimensions not omitted in pre-padded shape
474                        j = 0;
475                        for (int i = 0; i < or; i++) {
476                                if (Arrays.binarySearch(omitted, i) < 0) {
477                                        used[j++] = i;
478                                }
479                        }
480
481                        int[] vaxes = new int[uaxes.size()];
482                        j = 0;
483                        for (int i = 0; i < nr; i++) { // remap dimension numbering
484                                int l = uaxes.indexOf(i);
485                                if (l >= 0) {
486                                        vaxes[l] = used[j++];
487                                }
488                        }
489                        int[] taxes = new int[or];
490                        j = 0;
491                        for (int i = 0; i < or; i++) { // reassemble map
492                                if (Arrays.binarySearch(omitted, i) >= 0) {
493                                        taxes[i] = i;
494                                } else {
495                                        taxes[i] = vaxes[j++];
496                                }
497                        }
498
499                        naxes = taxes;
500                }
501
502                view.map = map == null ? naxes : calcTransposed(naxes, map);
503                if (view.size != 0) {
504                        // work out transposed (sliced) shape
505                        int[] tShape = calcTransposed(view.map, sShape == null ? oShape : sShape);
506                        try {
507                                view.padding = ShapeUtils.calcShapePadding(tShape, view.shape);
508                        } catch (IllegalArgumentException e) {
509                                System.err.println(e.getMessage() + ": " + Arrays.toString(tShape) + " cf " + Arrays.toString(view.shape));
510                        }
511                }
512                view.storeMetadata(metadata, Transposable.class);
513                view.transposeMetadata(axes);
514                return view;
515        }
516
517        private static int find(int[] map, int m, int off) {
518                for (int i = off, imax = map.length; i < imax; i++) {
519                        if (map[i] == m) {
520                                return i;
521                        }
522                }
523                return -1;
524        }
525
526        private static int[] calcTransposed(int[] map, int[] values) {
527                if (values == null) {
528                        return null;
529                }
530                int r = values.length;
531                if (map == null || r < 2) {
532                        return values;
533                }
534                int[] ovalues = new int[r];
535                for (int i = 0; i < r; i++) {
536                        ovalues[i] = values[map[i]];
537                }
538                return ovalues;
539        }
540
541        /**
542         * Calculate absolute slice
543         * @param slice
544         * @return true slice or null if zero-sized
545         */
546        protected final SliceND calcTrueSlice(SliceND slice) {
547                /*
548                 * Lazy dataset operations: getTransposedView (T), getSliceView (G), setShape/squeezeEnds (S+/S-):
549                 * 
550                 *     . T sets shape, base, and map in new view
551                 *     . G sets shape, size, begSlice and delSlice in new view
552                 *     . S sets shape, shapePadding in current view
553                 * 
554                 * Then getSlice needs to interpret all info to find true slice, load data, get transposition (view)
555                 * and set shape. Therefore:
556                 *     . S needs to update shapePadding only
557                 *     . T needs to update shapePadding too
558                 *     . G needs to work out true slice to update
559                 * 
560                 * slice -> true slice
561                 *   adjusts for shape (S^-1) then remap dimensions (T^-1)
562                 */
563
564                if (slice == null) {
565                        slice = new SliceND(shape);
566                }
567
568                if (ShapeUtils.calcLongSize(slice.getShape()) == 0) {
569                        return null;
570                }
571
572                int[] nshape;
573                int[] nstart;
574                int[] nstep;
575
576                int r = oShape.length;
577                if (padding == null) {
578                        nshape = slice.getShape();
579                        nstart = slice.getStart();
580                        nstep = slice.getStep();
581                } else {
582                        final int[] lshape = slice.getShape();
583                        final int[] lstart = slice.getStart();
584                        final int[] lstep  = slice.getStep();
585
586                        nstart = new int[r];
587                        nstep = new int[r];
588                        nshape = new int[r];
589                        int i = 0;
590                        int j = 0;
591                        for (int p : padding) { // remove padding
592                                if (p == 0) {
593                                        nshape[i] = lshape[j];
594                                        nstart[i] = lstart[j];
595                                        nstep[i]  = lstep[j];
596                                        i++;
597                                        j++;
598                                } else if (p < 0) {
599                                        int imax = i - p;
600                                        while (i < imax) {
601                                                nshape[i] = 1;
602                                                nstep[i]  = 1;
603                                                i++;
604                                        }
605                                } else {
606                                        j += p;
607                                }
608                        }
609                }
610
611                if (map != null && r > 1) { // transpose dimensions
612                        int[] pshape = new int[r];
613                        int[] pstart = new int[r];
614                        int[] pstep = new int[r];
615                        for (int i = 0; i < r; i++) {
616                                int m = map[i];
617                                pshape[m] = nshape[i];
618                                pstart[m] = nstart[i];
619                                pstep[m]  = nstep[i];
620                        }
621
622                        nshape = pshape;
623                        nstart = pstart;
624                        nstep  = pstep;
625                }
626
627                int[] nstop = new int[r];
628                if (begSlice != null) { // find net slice
629                        for (int i = 0; i < r; i++) {
630                                int b = begSlice[i];
631                                int d = delSlice[i];
632                                nstart[i] = b + nstart[i] * d;
633                                int nd = nstep[i] * d;
634                                nstep[i] = nd;
635                                nstop[i]  = nstart[i] + (nshape[i] - 1) * nd + (nd >= 0 ? 1 : -1);
636                        }
637                } else {
638                        for (int i = 0; i < r; i++) {
639                                int d = nstep[i];
640                                nstop[i] = nstart[i] + (nshape[i] - 1) * d + (d >= 0 ? 1 : -1);
641                        }
642                }
643
644                return createSlice(nstart, nstop, nstep);
645        }
646
647        protected SliceND createSlice(int[] nstart, int[] nstop, int[] nstep) {
648                return SliceND.createSlice(oShape, null, nstart, nstop, nstep);
649        }
650
651        /**
652         * Transform data so that it can be used in setSlice of saver
653         * @param data
654         * @param tslice true slice 
655         * @return data with dimensions adjusted and remapped 
656         */
657        final IDataset transformInput(IDataset data, SliceND tslice) {
658                if (padding != null) { // remove padding
659                        data = data.getSliceView();
660                        int[] nshape = tslice.getShape();
661                        data.setShape(nshape);
662                }
663
664                return map == null ? data : data.getTransposedView(map);
665        }
666
667        /**
668         * Store metadata items that has given annotation
669         * @param origMetadata
670         * @param aclazz
671         */
672        private void storeMetadata(Map<Class<? extends MetadataType>, List<MetadataType>> origMetadata, Class<? extends Annotation> aclazz) {
673                List<Class<? extends MetadataType>> mclazzes = findAnnotatedMetadata(aclazz);
674                if (mclazzes.size() == 0) {
675                        return;
676                }
677
678                if (oMetadata == null) {
679                        oMetadata = new HashMap<Class<? extends MetadataType>, List<MetadataType>>();
680                }
681                for (Class<? extends MetadataType> mc : mclazzes) {
682                        if (oMetadata.containsKey(mc)) {
683                                continue; // do not overwrite original
684                        }
685
686                        List<MetadataType> l = origMetadata.get(mc);
687                        List<MetadataType> nl = new ArrayList<MetadataType>(l.size());
688                        for (MetadataType m : l) {
689                                nl.add(m.clone());
690                        }
691                        oMetadata.put(mc, nl);
692                }
693        }
694
695        @SuppressWarnings("unchecked")
696        private List<Class<? extends MetadataType>> findAnnotatedMetadata(Class<? extends Annotation> aclazz) {
697                List<Class<? extends MetadataType>> mclazzes = new ArrayList<Class<? extends MetadataType>>();
698                if (metadata == null) {
699                        return mclazzes;
700                }
701
702                for (Class<? extends MetadataType> c : metadata.keySet()) {
703                        boolean hasAnn = false;
704                        for (MetadataType m : metadata.get(c)) {
705                                if (m == null) {
706                                        continue;
707                                }
708
709                                Class<? extends MetadataType> mc = m.getClass();
710                                do { // iterate over super-classes
711                                        for (Field f : mc.getDeclaredFields()) {
712                                                if (f.isAnnotationPresent(aclazz)) {
713                                                        hasAnn = true;
714                                                        break;
715                                                }
716                                        }
717                                        Class<?> sclazz = mc.getSuperclass();
718                                        if (!MetadataType.class.isAssignableFrom(sclazz)) {
719                                                break;
720                                        }
721                                        mc = (Class<? extends MetadataType>) sclazz;
722                                } while (!hasAnn);
723                                if (hasAnn) {
724                                        break;
725                                }
726                        }
727                        if (hasAnn) {
728                                mclazzes.add(c);
729                        }
730                }
731                return mclazzes;
732        }
733
734        /**
735         * Gets the maximum size of a slice of a dataset in a given dimension
736         * which should normally fit in memory. Note that it might be possible
737         * to get more in memory, this is a conservative estimate and seems to
738         * almost always work at the size returned; providing Xmx is less than
739         * the physical memory.
740         * 
741         * To get more in memory increase -Xmx setting or use an expression
742         * which calls a rolling function (like rmean) instead of slicing directly
743         * to memory.
744         * 
745         * @param lazySet
746         * @param dimension
747         * @return maximum size of dimension that can be sliced.
748         */
749        public static int getMaxSliceLength(ILazyDataset lazySet, int dimension) {
750                // size in bytes of each item
751                final double size = DTypeUtils.getItemBytes(DTypeUtils.getDTypeFromClass(lazySet.getElementClass()), lazySet.getElementsPerItem());
752                
753                // Max in bytes takes into account our minimum requirement
754                final double max  = Math.max(Runtime.getRuntime().totalMemory(), Runtime.getRuntime().maxMemory());
755                
756                // Firstly if the whole dataset it likely to fit in memory, then we allow it.
757                // Space specified in bytes per item available
758                final double space = max/lazySet.getSize();
759
760                // If we have room for this whole dataset, then fine
761                int[] shape = lazySet.getShape();
762                if (space >= size) {
763                        return shape[dimension];
764                }
765
766                // Otherwise estimate what we can fit in, conservatively.
767                // First get size of one slice, see it that fits, if not, still return 1
768                double sizeOneSlice = size; // in bytes
769                for (int dim = 0; dim < shape.length; dim++) {
770                        if (dim == dimension) {
771                                continue;
772                        }
773                        sizeOneSlice *= shape[dim];
774                }
775                double avail = max / sizeOneSlice;
776                if (avail < 1) {
777                        return 1;
778                }
779
780                // We fudge this to leave some room
781                return (int) Math.floor(avail/4d);
782        }
783}