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