    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}