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