Java source code of 'jhplot.math.Slicing'

package jhplot.math;



import java.util.Vector;



import static jhplot.math.LinearAlgebra.*;

/**
 * BSD License
 * 
 * @author Yann RICHET
 */
public class Slicing {

    public final static String AUTO_BOUNDS = "AUTO";

    public final static String UNIFORM_BOUNDS = "UNIFORM";

    public static double epsilon = 10E-7;

    private double[][] M;

    private int numDimensions;

    private Slice[] slice;

    /**
     * Construct a Slice from a matrix and an orthogonal slicing.
     * 
     * @param m
     *            Matrix to slice.
     * @param n
     *            Array of number of slices per dimension of the matrix.
     * @param bounds
     *            type of bounds setting : AUTO or UNIFORM.
     */

    public Slicing(double[][] m, int[] n, String bounds) {
        DoubleArray.checkColumnDimension(m, n.length);
        M = m;
        numDimensions = M[0].length;
        if (bounds == UNIFORM_BOUNDS) {
            setSlicesUniformBounds(n);
        } else if (bounds == AUTO_BOUNDS) {
            setSlicesAutoBounds(n);
        } else {
            throw new IllegalArgumentException("The bounds type : " + bounds + "is unknown. You must specify AUTO or UNIFORM.");
        }

    }

    /**
     * Construct a Slice from a matrix and an orthogonal slicing.
     * 
     * @param m
     *            Matrix to slice.
     * @param bounds
     *            bounds of slices per dimension (in rows!).
     */

    public Slicing(double[][] m, double[][] bounds) {
        DoubleArray.checkColumnDimension(m, bounds.length);
        M = m;
        numDimensions = M[0].length;
        setSlicesBounds(bounds);
    }

    /**
     * Construct a Slice from a matrix and an orthogonal slicing.
     * 
     * @param m
     *            Matrix to slice.
     * @param centers
     *            centers of slices per dimension (in rows!).
     * @param widths
     *            widths of slices per dimension (in rows!).
     */

    public Slicing(double[][] m, double[][] centers, double[][] widths) {
        DoubleArray.checkColumnDimension(m, centers.length);
        M = m;
        numDimensions = M[0].length;
        setSlicesCentersnWidths(centers, widths);
    }

    /**
     * Construct a Slice from a matrix and an orthogonal slicing.
     * 
     * @param m
     *            Matrix to slice.
     * @param n
     *            Array of slices.
     */

    public Slicing(double[][] m, int[][] n) {
        M = m;
        numDimensions = M[0].length;
        setSlices(n);
    }

    public double[][] getSlicingMatrix() {
        double[][] X = new double[slice.length][numDimensions * 2 + 1];
        for (int i = 0; i < slice.length; i++) {
            System.arraycopy(slice[i].center, 0, X[i], 0, numDimensions);
            X[i][numDimensions] = slice[i].cardinal;
            System.arraycopy(slice[i].width, 0, X[i], numDimensions + 1, numDimensions);
        }
        return X;
    }

    public double[] getSlicesCardinals() {
        double[] cards = new double[slice.length];
        for (int i = 0; i < slice.length; i++) {
            cards[i] = slice[i].cardinal;
        }
        return cards;
    }

    // public double[][] getSlicesSigmaCardinals() {
    // AbstractMatrix cards = new Matrix(slice.length, 1);
    // for (int i = 0; i < slice.length; i++) {
    // cards.set(i, 0, slice[i].cardinal);
    // }
    // return cards;
    // }

    public double[][] getSlicesWidths() {
        double[][] w = new double[slice.length][numDimensions];
        for (int i = 0; i < slice.length; i++) {
            System.arraycopy(slice[i].width, 0, w[i], 0, numDimensions);
        }
        return w;
    }

    public double[][] getSlicesCenters() {
        double[][] c = new double[slice.length][numDimensions];
        for (int i = 0; i < slice.length; i++) {
            System.arraycopy(slice[i].center, 0, c[i], 0, numDimensions);
        }
        return c;
    }

    public int findSlice(double[] x) {
        DoubleArray.checkLength(x, numDimensions);
        int s = -1;
        for (int i = 0; i < slice.length; i++) {
            if (slice[i].isIn(x)) {
                s = i;
            }
        }
        return s;
    }

    public int[][] getSlicesIndexes() {
        int[][] s = new int[slice.length][0];
        for (int i = 0; i < slice.length; i++) {
            s[i] = slice[i].getIndexesArray();
        }
        return s;
    }

    /**
     * Method used to build slices centers and widths from a definied number of
     * slices per dimension.
     * 
     * @param bounds
     *            bounds of the slices
     */

    private void setSlicesBounds(double[][] bounds) {
        DoubleArray.checkRowDimension(bounds, numDimensions);

        int[] numberSlicesPerDimension = new int[numDimensions];
        for (int i = 0; i < numDimensions; i++) {
            numberSlicesPerDimension[i] = bounds[i].length - 1;
        }

        slice = new Slice[cumProd(numberSlicesPerDimension)];

        int[] counter = new int[numDimensions];
        double[] sliceWidth = new double[numDimensions];
        double[] sliceCenter = new double[numDimensions];

        for (int i = 0; i < slice.length; i++) {
            for (int j = 0; j < numDimensions; j++) {
                sliceWidth[j] = bounds[j][counter[j] + 1] - bounds[j][counter[j]];
                sliceCenter[j] = (bounds[j][counter[j] + 1] + bounds[j][counter[j]]) / 2;
            }

            slice[i] = new Slice(sliceCenter, sliceWidth);
            // slice[i].toCommandeLine("slice "+i);

            if (i < (slice.length - 1)) {
                incCounter(counter, numberSlicesPerDimension);
            }
        }

        // for slicesCardinals & elementsIndexes & slices
        countFromBounds();
    }

    /**
     * Method used to build slices centers and widths from a definied number of
     * slices per dimension.
     * 
     * @param centers
     *            centers of slices per dimension.
     * @param widths
     *            widths of slices per dimension.
     */

    private void setSlicesCentersnWidths(double[][] centers, double[][] widths) {
        DoubleArray.checkRowDimension(centers, numDimensions);
        DoubleArray.checkRowDimension(widths, numDimensions);

        int[] numberSlicesPerDimension = new int[numDimensions];
        for (int i = 0; i < numDimensions; i++) {
            numberSlicesPerDimension[i] = centers[i].length;
        }

        slice = new Slice[cumProd(numberSlicesPerDimension)];

        int[] counter = new int[numDimensions];
        double[] sliceWidth = new double[numDimensions];
        double[] sliceCenter = new double[numDimensions];

        for (int i = 0; i < slice.length; i++) {
            for (int j = 0; j < numDimensions; j++) {
                sliceWidth[j] = widths[j][counter[j]];
                sliceCenter[j] = centers[j][counter[j]];
            }

            slice[i] = new Slice(sliceCenter, sliceWidth);
            // slice[i].toCommandLine("slice " + i);

            if (i < (slice.length - 1)) {
                incCounter(counter, numberSlicesPerDimension);
            }
        }

        // for slicesCardinals & elementsIndexes & slices
        countFromBounds();
    }

    /**
     * Method used to build slices centers and widths from a definied number of
     * slices per dimension. each slice have the same width : Uniform bounds
     * 
     * @param numberSlicesPerDimension
     *            array of number of slices per dimension of the matrix to slice
     */

    private void setSlicesUniformBounds(int[] numberSlicesPerDimension) {
        slice = new Slice[cumProd(numberSlicesPerDimension)];

        int[] counter = new int[numDimensions];
        double[] sliceWidth = new double[numDimensions];
        double[] sliceCenter = new double[numDimensions];

        double[] Mmin = DoubleArray.min(M);
        double[] Mmax = DoubleArray.max(M);
        double[] pitch = new double[numDimensions];
        for (int j = 0; j < numDimensions; j++) {
            pitch[j] = (Mmax[j] - Mmin[j]) / numberSlicesPerDimension[j];
        }

        for (int i = 0; i < slice.length; i++) {
            for (int j = 0; j < numDimensions; j++) {
                // double min = M.getColumn(j).min().toDouble();
                // double max = M.getColumn(j).max().toDouble();
                // double pitch = (max - min) / numberSlicesPerDimension[j];
                // sliceWidth.set(0, j, pitch);
                // sliceCenter.set(0, j, min + (counter[j] + 0.5) * pitch);
                sliceWidth[j] = pitch[j];
                sliceCenter[j] = Mmin[j] + (counter[j] + 0.5) * pitch[j];
            }

            slice[i] = new Slice(sliceCenter, sliceWidth);
            // slice[i].toCommandLine("slice "+i);

            if (i < (slice.length - 1)) {
                incCounter(counter, numberSlicesPerDimension);
            }
        }

        // for slicesCardinals & elementsIndexes & slices
        countFromBounds();
    }

    /**
     * Method used to build slices centers and widths from a definied number of
     * slices per dimension. each slice have same number of elements : Auto
     * bounds
     * 
     * @param numberSlicesPerDimension
     *            array of number of slices per dimension of the matrix to slice
     */

    private void setSlicesAutoBounds(int[] numberSlicesPerDimension) {
        slice = new Slice[cumProd(numberSlicesPerDimension)];

        int[] counter = new int[numDimensions];
        double[] sliceWidth = new double[numDimensions];
        double[] sliceCenter = new double[numDimensions];

        // evaluate centers and width per dimension
        int[] numberOfElement = new int[numDimensions];
        double[][] centers = new double[numDimensions][];
        double[][] widths = new double[numDimensions][];
    //    int[] sortIndex;
        for (int j = 0; j < numDimensions; j++) {
            double[] column = DoubleArray.getColumnCopy(M, j);
            numberOfElement[j] = column.length / numberSlicesPerDimension[j];

            new Sorting(column, false);

            centers[j] = new double[numberSlicesPerDimension[j]];
            widths[j] = new double[numberSlicesPerDimension[j]];

            int i_min;
            int i_max = -1;
            for (int i = 0; i < numberSlicesPerDimension[j] - 1; i++) {
                // System.out.println("elements : " + (i * numberOfElement[j]) +
                // " -> " + ( (i +
                // 1) * numberOfElement[j] - 1));
                // System.out.println("i=" + i + " j=" + j + "
                // numberOfElement[j]=" + numberOfElement[j]);

                i_min = i_max + 1;
                i_max = Math.max((i + 1) * numberOfElement[j] - 1, i_min);
                // in case column[i_max] == column[i_max + 1], the two slices i
                // and i+1 contains the same two elements, so an error will be
                // found in countFromBounds()
                // so I decided to add column[i_max + 1] in the slice i and
                // offset the bound of slice i+1
                try {
                    while (column[i_max] == column[i_max + 1]) {
                        i_max++;
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new IllegalArgumentException("Too much slices defined for the values to slice... you should try to reduce the nyumber of slices !");
                }

                centers[j][i] = (column[i_min] + column[i_max]) / 2;
                widths[j][i] = column[i_max] - column[i_min];
            }
            // the last slice must contains all remaining elements...
            int i = numberSlicesPerDimension[j] - 1;
            i_min = i_max + 1;
            i_max = column.length - 1;

            if (i_min > i_max) {
                throw new IllegalArgumentException("Too much slices defined for the values to slice... you should try to reduce the nyumber of slices !");
            }

            centers[j][i] = (column[i_min] + column[i_max]) / 2;
            widths[j][i] = column[i_max] - column[i_min];
        }

        for (int i = 0; i < slice.length; i++) {
            for (int j = 0; j < numDimensions; j++) {
                sliceWidth[j] = widths[j][counter[j]];
                sliceCenter[j] = centers[j][counter[j]];
            }

            slice[i] = new Slice(sliceCenter, sliceWidth);
            // slice[i].toCommandLine("slice " + i + " !");

            if (i < (slice.length - 1)) {
                incCounter(counter, numberSlicesPerDimension);
            }
        }

        // for slicesCardinals & elementsIndexes & slices
        countFromBounds();
    }

    /**
     * Method used to build slices centers and widths from a definied number of
     * slices per dimension. Warning : no test is performed to verify that your
     * slices are not superimposed !!!
     * 
     * @param s
     *            slices of the matrix
     */

    private void setSlices(int[][] s) {
        slice = new Slice[s.length];

        double[] sliceWidth = new double[numDimensions];
        double[] sliceCenter = new double[numDimensions];

        double[][] tmp;
        double[] tmpMax;
        double[] tmpMin;
        for (int i = 0; i < slice.length; i++) {
            tmp = DoubleArray.getRowsCopy(M, s[i]);
            tmpMax = DoubleArray.max(tmp);
            tmpMin = DoubleArray.min(tmp);
            sliceWidth = minus(tmpMax, tmpMin);
            sliceCenter = times(plus(tmpMax, tmpMin), 0.5);
            slice[i] = new Slice(sliceCenter, sliceWidth);

            // for slicesCardinals & elementsIndexes & slices
            for (int j = 0; j < tmp.length; j++) {
                slice[i].add(DoubleArray.getRowCopy(tmp, j), s[i][j]);
            }
        }
    }

    /**
     * Method used to build slices cardinales and indexes from pre-defined
     * slices centers and widths.
     */

    private void countFromBounds() {

        int numOE = M.length;
        // int in;
        for (int i = 0; i < numOE; i++) {
            Vector inv = new Vector(0);
            // in = 0;
            for (int j = 0; j < slice.length; j++) {
                if (slice[j].isIn(DoubleArray.getRowCopy(M, i))) {
                    // if more than one slice contains the current element:
                    // M.getRow(i), it will be ignored...
                    // very dangerous if you're not sure of your slicing !!!
                    // just comment the following line (and it's closing braket)
                    // if you want to make the test "more than one slice
                    // containg"
                    if (inv.size() == 0) {
                        slice[j].add(DoubleArray.getRowCopy(M, i), i);
                    }
                    inv.add(new Integer(j));

                    // System.out.println(M.getRow(i).toString()+" is in group
                    // "+j);
                } else {
                    // System.out.println(M.getRow(i).toString()+" is not in
                    // group "+j);
                }
            }
            // "more than one slice containg" TEST
            // this test may be not passed if your slicing is "AUTO" i.e. if
            // slices are bounded by elements
            // if (inv.size() > 1) {
            //
            // String S = "";
            // for (int j = 0; j < inv.size(); j++) {
            // S += slice[ ( (Integer) inv.get(j)).intValue()].toString("slice["
            // + j + "]");
            // }
            //
            // System.err.println("The element = " + M.getRow(i).toString() +
            // " is counted in the first slice of these " + inv.size() + "
            // slices :" + S);
            //
            // }

            // "no slice containg" TEST
            if (inv.size() == 0) {
                String S = "";
                for (int j = 0; j < slice.length; j++) {
                    S += slice[ /* ( (Integer) inv.get(j)).intValue() */j].toString("slice[" + j + "]");
                }

                throw new IllegalArgumentException("The element = " + DoubleArray.toString(DoubleArray.getRowsCopy(M, i, i)) + " is in 0 slices :" + S);

            }
        }
    }

    /**
     * Returns the cumulative product of an integer array.
     * 
     * @param a
     *            array of integer to product
     * @return a[0]*a[1]*...*a[n]
     */

    private static int cumProd(int[] a) {
        int res = 1;
        for (int i = 0; i < a.length; i++) {
            res = res * a[i];
        }
        return res;
    }

    /**
     * Incrementation of a counter in a non-decimal base.
     * 
     * @param counter
     *            counter to increment
     * @param counterMaxs
     *            base of each dimension of the counter
     */

    private void incCounter(int[] counter, int[] counterMaxs) {
        // System.out.print("\n"+arrayToString(counter)+ " /
        // "+arrayToString(counterMaxs));
        int decToInc = 0;
        for (int i = 0; i < counter.length; i++) {
            if (counter[i] < counterMaxs[i] - 1) {
                decToInc = i;
                break;
            } else {
                decToInc++;
            }
        }

        counter[decToInc]++;
        for (int i = 0; i < decToInc; i++) {
            counter[i] = 0;
        }
        // System.out.print(" -> "+arrayToString(counter)+ " /
        // "+arrayToString(counterMaxs));
    }

    /*
    private String arrayToString(int[] array) {
        StringBuffer s = new StringBuffer();
        for (int i = 0; i < array.length; i++) {
            s.append(" " + array[i]);
        }
        return s.toString();
    }

 */
    
    public void toCommandLine(String title) {
        System.out.println("\nSlicing " + title + " :");
        for (int i = 0; i < slice.length; i++) {
            slice[i].toCommandLine("Slice " + i);
        }
    }

    private class Slice {

        public Vector indexes;

        public int cardinal;

        public double[] center;

        public double[] width;

        public Slice(double[] c, double[] w) {
            center = DoubleArray.copy(c);
            width = DoubleArray.copy(w);
            indexes = new Vector();
        }

        public boolean isIn(double[] x) {
            boolean in = true;
            for (int j = 0; j < x.length; j++) {
                // exact test
                // boolean inj = ( (x.get(0, j) >= (center.get(0, j) -
                // (width.get(0, j) / 2))) &&
                // (x.get(0, j) <= (center.get(0, j) + (width.get(0, j) / 2))));

                // not exact test, needed for precision problems. Set epsilon to
                // 0 if you want an exact test.
                boolean inj = ((Math.abs(x[j] - center[j]) - width[j] / 2) <= epsilon);

                in = in && inj;
            }
            return in;
        }

        public void add(double[] x, int i) {
            // if (!isIn(x)) throw new IllegalArgumentException("Element x=
            // "+x.toString()+" is not include in this slice !");
            cardinal++;
            indexes.add(new Integer(i));
        }

        public int[] getIndexesArray() {
            int[] array = new int[indexes.size()];
            for (int i = 0; i < array.length; i++) {
                array[i] = ((Integer) (indexes.get(i))).intValue();
            }
            return array;
        }

        public String toString(String s) {
            StringBuffer st;
            st = new StringBuffer("\n" + s);
            st.append("  contains " + cardinal + " elements\n");
            // st.append(" center = " + DoubleArray.toString(center) + " width =
            // " +
            // DoubleArray.toString(width));
            st.append("  min = " + DoubleArray.toString(minus(center, times(width, 0.5))) + "  max = " + DoubleArray.toString(plus(center, times(width, 0.5)))
                    + "\n");
            st.append("  elements indices : \n");
            for (int i = 0; i < indexes.size(); i++) {
                st.append(" " + ((Integer) (indexes.get(i))).intValue());
            }
            return st.toString();
        }

        public void toCommandLine(String s) {
            System.out.println(toString(s));
        }
    }

    public static void main(String[] args) {
        // double[][] X = DoubleArray.random(100, 1,0,1);
        // //X.toCommandLine("X");
        //
        // for (int i = 0; i < 90; i++) {
        // int i1 = (int) (Math.random() * 100);
        // int i2 = (int) (Math.random() * 100);
        // X[i1][0]= X[i2][0];
        // }
        //
        // Slicing s1 = new Slicing(X, new int[] {50}
        // , "AUTO");
        // s1.toCommandLine("");
        //
        // //double[][] X = DoubleArray.random(10, 2, 0, 1);
        // double[][] X = { {0.1150333685, 0.2249385189}
        // , {0.1010784039, 0.0801458224}
        // , {0.4389139657, 0.3090770470}
        // , {0.3227165908, 0.2137681387}
        // , {0.0439011003, 0.8825942143}
        // , {0.0435162036, 0.6976747539}
        // , {0.8164387558, 0.3543210610}
        // , {0.6281565206, 0.3976606322}
        // , {0.2258957129, 0.1568657272}
        // , {0.3836445557, 0.9809615312}
        // };
        //
        // DoubleArray.toCommandLine(X, "X");
        //
        // Slicing s1 = new Slicing(X, new int[] {3, 2}
        // , "AUTO");
        // s1.toCommandLine("");
        //
        // double[] boundsX = new double[] {0,0.1,0.5,0.9,1};
        // double[] boundsY = new double[] {0,0.5,1};
        // Slicing s2 = new Slicing(X,new double[][] {boundsX,boundsY});
        // s2.toCommandLine("");
        //
        // Slicing s3 = new Slicing(X,new int[][] {{0,1,2,3},{4,5,6,7,8},{9}});
        // s3.toCommandLine("");
        //
        // Matrix centersX = new Matrix(new double[] {0.05, 0.15, 0.25, 0.35,
        // 0.45, 0.55, 0.65, 0.75, 0.85, 0.95}
        // , 10);
        // Matrix widthsX = new Matrix(new double[] {0.1, 0.1, 0.1, 0.1, 0.1,
        // 0.1, 0.1, 0.1, 0.1, 0.1}
        // , 10);
        // Matrix centersY = new Matrix(new double[] {0.1, 0.3, 0.5, 0.7, 0.9}
        // , 5);
        // Matrix widthsY = new Matrix(new double[] {0.2, 0.2, 0.2, 0.2, 0.2}
        // , 5);
        // Slicing s4 = new Slicing(X, new Matrix[] {centersX, centersY}
        // , new Matrix[] {widthsX, widthsY});
        // s4.toCommandLine("");

    }

}