001/*-
002 *******************************************************************************
003 * Copyright (c) 2011, 2017 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 *    Tom Schoonjans - min and max methods
012 *******************************************************************************/
013
014package org.eclipse.january.dataset;
015
016import java.util.Arrays;
017
018import org.eclipse.january.DatasetException;
019
020/**
021 * Mathematics class for lazy datasets
022 */
023public final class LazyMaths {
024
025        private LazyMaths() {
026
027        }
028
029        /**
030         * Setup the logging facilities
031         */
032//      private static final Logger logger = LoggerFactory.getLogger(LazyMaths.class);
033
034        // TODO Uncomment this next line when minimum JDK is set to 1.8
035        // @FunctionalInterface
036        private static interface IMathOperation {
037                void execute(IDataset a, IDataset b, Dataset c);
038        }
039
040        private enum MathOperation implements IMathOperation {
041                // TODO use lambdas here when moving to Java 8
042                MAX(new IMathOperation() {
043                        @Override
044                        public void execute(IDataset a, IDataset b, Dataset c) {
045                                Maths.maximum(a, b, c);
046                        }
047                }, "maximum"),
048                MIN(new IMathOperation() {
049                        @Override
050                        public void execute(IDataset a, IDataset b, Dataset c) {
051                                Maths.minimum(a, b, c);
052                        }
053                }, "minimum");
054
055                private final IMathOperation operation;
056                private final String operationName;
057
058                private MathOperation(IMathOperation operation, String operationName) {
059                        this.operation = operation;
060                        this.operationName = operationName;
061                }
062                
063                @Override
064                public void execute(IDataset a, IDataset b, Dataset c) {
065                        operation.execute(a, b, c);
066                }
067
068                /**
069                 * @return the operationName
070                 */
071                public String getOperationName() {
072                        return operationName;
073                }
074
075        }
076
077        private static Dataset maxmin(final ILazyDataset data, MathOperation operation, int[] axes) throws DatasetException {
078                // we will be working here with the "ignoreAxes" instead to improve performance dramatically
079                int[] ignoreAxes;
080                if (axes.length == 0) {
081                        ignoreAxes = axes;
082                } else {
083                        ignoreAxes = ShapeUtils.getRemainingAxes(data.getRank(), axes);
084                }
085
086                final int[] oldShape = data.getShape();
087
088                SliceND sa = new SliceND(oldShape);
089                SliceNDIterator it = new SliceNDIterator(sa, ignoreAxes);
090                Dataset result = null;
091                
092                while (it.hasNext()) {
093                        SliceND currentSlice = it.getCurrentSlice();
094                        IDataset slice = data.getSlice(currentSlice);
095                        if (result == null) {
096                                result = DatasetUtils.convertToDataset(slice);
097                        } else {
098                                operation.execute(result, slice, result);
099                        }
100                }
101                if (result != null) {
102                        result.setName(operation.getOperationName());
103                        result.squeeze();
104                }
105                return result;
106        }
107
108        /**
109         * @param data lazy dataset
110         * @param axes (can be negative). If null or empty then use all axes
111         * @return maximum along axes in lazy dataset
112         * @throws DatasetException when data cannot be retrieved
113         * @since 2.1
114         */
115        public static Dataset max(final ILazyDataset data, int... axes) throws DatasetException {
116                if (data instanceof Dataset) {
117                        Dataset tmp = (Dataset) data;
118                        axes = ShapeUtils.checkAxes(data.getRank(), axes);
119                        return tmp.max(axes);
120                }
121                return maxmin(data, MathOperation.MAX, axes);
122        }
123
124        /**
125         * @param data lazy dataset
126         * @param axes (can be negative). If null or empty then use all axes
127         * @return minimum along axes in lazy dataset
128         * @throws DatasetException when data cannot be retrieved
129         * @since 2.1
130         */
131        public static Dataset min(final ILazyDataset data, int... axes) throws DatasetException {
132                if (data instanceof Dataset) {
133                        Dataset tmp = (Dataset) data;
134                        axes = ShapeUtils.checkAxes(data.getRank(), axes);
135                        return tmp.min(axes);
136                }
137                return maxmin(data, MathOperation.MIN, axes);
138        }
139
140        /**
141         * @param data lazy dataset
142         * @param axis (can be negative)
143         * @return sum along axis in lazy dataset
144         * @throws DatasetException when data cannot be retrieved
145         */
146        public static Dataset sum(final ILazyDataset data, int axis) throws DatasetException {
147                if (data instanceof Dataset) {
148                        return ((Dataset) data).sum(axis);
149                }
150                int[][] sliceInfo = new int[3][];
151                int[] shape = data.getShape();
152                final Dataset result = prepareDataset(axis, shape, sliceInfo);
153
154                final int[] start = sliceInfo[0];
155                final int[] stop = sliceInfo[1];
156                final int[] step = sliceInfo[2];
157                final int length = shape[axis];
158
159                for (int i = 0; i < length; i++) {
160                        start[axis] = i;
161                        stop[axis] = i + 1;
162                        result.iadd(data.getSlice(start, stop, step));
163                }
164
165                result.setShape(ShapeUtils.squeezeShape(shape, axis));
166                return result;
167        }
168
169        /**
170         * @param data lazy dataset
171         * @param ignoreAxes axes to ignore
172         * @return sum when given axes are ignored in lazy dataset
173         * @throws DatasetException when data cannot be retrieved
174         * @since 2.0
175         */
176        public static Dataset sum(final ILazyDataset data, int... ignoreAxes) throws DatasetException {
177                return sum(data, true, ignoreAxes);
178        }
179
180        /**
181         * @param data lazy dataset
182         * @param ignore if true, ignore the provided axes, otherwise use only the provided axes
183         * @param axes axes to ignore or accept, depending on the preceding flag
184         * @return sum over all used axes
185         * @throws DatasetException when data cannot be retrieved
186         * @since 2.0
187         */
188        public static Dataset sum(final ILazyDataset data, boolean ignore, int... axes) throws DatasetException {
189                ILazyDataset rv = data;
190                
191                if (ignore) {
192                        axes = ShapeUtils.getRemainingAxes(data.getRank(), axes);
193                } else {
194                        axes = ShapeUtils.checkAxes(data.getRank(), axes);
195                }
196                for (int i = 0 ; i < axes.length ; i++) {
197                        rv = sum(rv, axes[i] - i);
198                }
199
200                return DatasetUtils.sliceAndConvertLazyDataset(rv);
201        }
202
203        /**
204         * @param data lazy dataset
205         * @param axis (can be negative)
206         * @return product along axis in lazy dataset
207         * @throws DatasetException when data cannot be retrieved
208         */
209        public static Dataset product(final ILazyDataset data, int axis) throws DatasetException {
210                int[][] sliceInfo = new int[3][];
211                int[] shape = data.getShape();
212                final Dataset result = prepareDataset(axis, shape, sliceInfo);
213                result.fill(1);
214
215                final int[] start = sliceInfo[0];
216                final int[] stop = sliceInfo[1];
217                final int[] step = sliceInfo[2];
218                final int length = shape[axis];
219
220                for (int i = 0; i < length; i++) {
221                        start[axis] = i;
222                        stop[axis] = i + 1;
223                        result.imultiply(data.getSlice(start, stop, step));
224                }
225
226                result.setShape(ShapeUtils.squeezeShape(shape, axis));
227                return result;
228        }
229
230        /**
231         * @param start number to begin at
232         * @param stop inclusive
233         * @param data lazy dataset
234         * @param ignoreAxes axes to ignore
235         * @return mean when given axes are ignored in lazy dataset
236         * @throws DatasetException when data cannot be retrieved
237         */
238        public static Dataset mean(int start, int stop, ILazyDataset data, int... ignoreAxes) throws DatasetException {
239                int[] shape = data.getShape();
240                PositionIterator iter = new PositionIterator(shape, ignoreAxes);
241                int[] pos = iter.getPos();
242                boolean[] omit = iter.getOmit();
243
244                int rank = shape.length;
245                int[] st = new int[rank];
246                Arrays.fill(st, 1);
247                int[] end = new int[rank];
248
249                RunningAverage av = null;
250                int c = 0;
251                while (iter.hasNext() && c < stop) {
252                        if (c++ < start) continue;
253                        for (int i = 0; i < rank; i++) {
254                                end[i] = omit[i] ? shape[i] : pos[i] + 1;
255                        }
256                        IDataset ds = data.getSlice(pos, end, st);
257                        if (av == null) {
258                                av = new RunningAverage(ds);
259                        } else {
260                                av.update(ds);
261                        }
262                }
263
264                return  av != null ? av.getCurrentAverage().squeeze() : null;
265        }
266
267        /**
268         * @param data lazy dataset
269         * @param ignoreAxes axes to ignore
270         * @return mean when given axes are ignored in lazy dataset
271         * @throws DatasetException when data cannot be retrieved
272         */
273        public static Dataset mean(ILazyDataset data, int... ignoreAxes) throws DatasetException {
274                return mean(0, Integer.MAX_VALUE -1 , data, ignoreAxes);
275        }
276
277        private static Dataset prepareDataset(int axis, int[] shape, int[][] sliceInfo) {
278                int rank = shape.length;
279                axis = ShapeUtils.checkAxis(rank, axis);
280
281                sliceInfo[0] = new int[rank];
282                sliceInfo[1] = shape.clone();
283                sliceInfo[2] = new int[rank];
284                Arrays.fill(sliceInfo[2], 1);
285
286                final int[] nshape = shape.clone();
287                nshape[axis] = 1;
288
289                return DatasetFactory.zeros(nshape);
290        }
291}