001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.math.matrix;
031
032import no.uib.cipr.matrix.DenseMatrix;
033import no.uib.cipr.matrix.DenseVector;
034
035import org.netlib.lapack.LAPACK;
036import org.netlib.util.intW;
037import org.openimaj.util.array.ArrayUtils;
038import org.openimaj.util.pair.IndependentPair;
039
040import Jama.Matrix;
041
042/**
043 * Methods for solving the Generalised Eigenvalue Problem: A x = L B x.
044 * <p>
045 * Internally the methods in this class use LAPACK to compute the solutions.
046 * 
047 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
048 *
049 */
050public class GeneralisedEigenvalueProblem {
051    private final static int sygvd(int itype, String jobz, String uplo, DenseMatrix A, DenseMatrix B, DenseVector W) {
052        int info = dsygvd(itype, jobz, uplo, A.numColumns(), A.getData(), A.numRows(), B.getData(), B.numRows(), W.getData());
053        if (info == 0) {
054            return 0;
055        } else {
056            if (info < 0) throw new IllegalArgumentException("LAPACK ERROR: DSYGVD returned " + info);
057            throw new RuntimeException("LAPACK ERROR: DSYGVD returned " + info);
058        }
059    }
060    
061    private final static int dsygvd(int itype, String jobz, String uplo, int n, double[] a, int lda, double[] b, int ldb, double[] w) {
062      double[] work = new double[1];
063      double[] tmp = new double[1];
064      intW info = new intW(-1);
065      int lwork;
066      int[] iwork = new int[1];
067      int liwork;
068      
069      //call with info=-1 to determine size of working space
070      LAPACK.getInstance().dsygvd(itype, jobz, uplo, n, tmp, lda, tmp, ldb, tmp, work, -1, iwork, 0, info);
071      
072      if (info.val != 0)
073        return info.val; //an error occurred
074      
075      //setup working space
076      lwork = (int) work[0]; 
077      work = new double[lwork];
078      liwork = (int) iwork[0]; 
079      iwork = new int[liwork];
080      //and do the work
081      LAPACK.getInstance().dsygvd(itype, jobz, uplo, n, a, lda, b, ldb, w, work, lwork, iwork, liwork, info);
082      
083      return info.val;
084    }
085    
086        /**
087     * Compute the generalised eigenvalues, L, of the problem A x = L B x.
088     * The returned eigenvalues are not ordered.
089     *
090     * @param A symmetric Matrix A; only the upper triangle is used.
091     * @param B symmetric Matrix B; only the upper triangle is used.
092     * @return the eigenvalues L.
093     */
094    public static DenseVector symmetricGeneralisedEigenvalues(DenseMatrix A, DenseMatrix B) {
095        if (!A.isSquare() || !B.isSquare()) 
096                throw new IllegalArgumentException("Input matrices must be square");
097        
098        DenseVector W = new DenseVector(A.numRows());
099        sygvd(1, "N", "U", A.copy(), B.copy(), W);
100        
101        return W;
102    }
103    
104    /**
105     * Solve the general problem A x = L B x.
106     * The returned eigenvalues are not ordered.
107     *
108     * @param A symmetric matrix A
109     * @param B symmetric matrix B
110     * @return The eigenvectors x and eigenvalues L.
111     */
112    public static IndependentPair<DenseMatrix, DenseVector> symmetricGeneralisedEigenvectors(DenseMatrix A, DenseMatrix B) {
113        if (!A.isSquare() || !B.isSquare()) 
114                throw new IllegalArgumentException("Input matrices must be square");
115        
116        DenseMatrix vecs = A.copy();
117        DenseVector W = new DenseVector(A.numRows());
118        
119        sygvd(1, "V", "U", vecs, B.copy(), W);
120        
121        return new IndependentPair<DenseMatrix, DenseVector>(vecs, W);
122    }
123    
124    /**
125     * Compute the generalised eigenvalues, L, of the problem A x = L B x.
126     * The returned eigenvalues are not ordered.
127     *
128     * @param A symmetric Matrix A; only the upper triangle is used.
129     * @param B symmetric Matrix B; only the upper triangle is used.
130     * @return the eigenvalues L.
131     */
132    public static double[] symmetricGeneralisedEigenvalues(Matrix A, Matrix B) {
133        if ((A.getRowDimension() != A.getColumnDimension()) || (B.getRowDimension() != B.getColumnDimension())) 
134                throw new IllegalArgumentException("Input matrices must be square");
135        
136        DenseVector W = new DenseVector(A.getRowDimension());
137        sygvd(1, "N", "U", new DenseMatrix(A.getArray()), new DenseMatrix(B.getArray()), W);
138        
139        return W.getData();
140    }
141    
142    /**
143     * Solve the general problem A x = L B x. 
144     * The returned eigenvalues are not ordered.
145     *
146     * @param A symmetric matrix A
147     * @param B symmetric matrix B
148     * @return The eigenvectors x and eigenvalues L.
149     */
150    public static IndependentPair<Matrix, double[]> symmetricGeneralisedEigenvectors(Matrix A, Matrix B) {
151        if ((A.getRowDimension() != A.getColumnDimension()) || (B.getRowDimension() != B.getColumnDimension())) 
152                throw new IllegalArgumentException("Input matrices must be square");
153        
154        int dim = A.getRowDimension();
155        DenseMatrix vecs = new DenseMatrix(A.getArray());
156        DenseVector W = new DenseVector(dim);
157        
158        sygvd(1, "V", "U", vecs, new DenseMatrix(B.getArray()), W);
159        
160        Matrix evecs = new Matrix(dim, dim);
161        final double[][] evecsData = evecs.getArray();
162        final double[] vecsData = vecs.getData();
163        for (int r=0; r<dim; r++)
164                for (int c=0; c<dim; c++)
165                        evecsData[r][c] = vecsData[r + c * dim];
166        
167        return new IndependentPair<Matrix, double[]>(evecs, W.getData());
168    }
169    
170    /**
171     * Solve the general problem A x = L B x. 
172     * The returned eigenvalues ordered in a decreasing manner.
173     *
174     * @param A symmetric matrix A
175     * @param B symmetric matrix B
176     * @return The eigenvectors x and eigenvalues L.
177     */
178    public static IndependentPair<Matrix, double[]> symmetricGeneralisedEigenvectorsSorted(Matrix A, Matrix B) {
179        if ((A.getRowDimension() != A.getColumnDimension()) || (B.getRowDimension() != B.getColumnDimension())) 
180                throw new IllegalArgumentException("Input matrices must be square");
181        
182        int dim = A.getRowDimension();
183        DenseMatrix vecs = new DenseMatrix(A.getArray());
184        DenseVector W = new DenseVector(dim);
185        
186        sygvd(1, "V", "U", vecs, new DenseMatrix(B.getArray()), W);
187        
188        Matrix evecs = new Matrix(dim, dim);
189        final double[][] evecsData = evecs.getArray();
190        final double[] vecsData = vecs.getData();
191        final double[] valsData = W.getData();
192        
193        int [] indices = ArrayUtils.range(0, valsData.length-1);
194        ArrayUtils.parallelQuicksortDescending(valsData, indices);
195        
196        for (int r=0; r<dim; r++)
197                for (int c=0; c<dim; c++)
198                        evecsData[r][c] = vecsData[r + indices[c] * dim];
199        
200        return new IndependentPair<Matrix, double[]>(evecs, valsData);
201    }
202    
203    /**
204     * Solve the general problem A x = L B x. 
205     * The returned eigenvalues ordered in a decreasing manner. Only the
206     * top numVecs eigenvalues and vectors are returned.
207     *
208     * @param A symmetric matrix A
209     * @param B symmetric matrix B
210     * @param numVecs number of eigenvalues/eigenvectors
211     * @return The eigenvectors x and eigenvalues L.
212     */
213    public static IndependentPair<Matrix, double[]> symmetricGeneralisedEigenvectorsSorted(Matrix A, Matrix B, int numVecs) {
214        if ((A.getRowDimension() != A.getColumnDimension()) || (B.getRowDimension() != B.getColumnDimension())) 
215                throw new IllegalArgumentException("Input matrices must be square");
216        
217        int dim = A.getRowDimension();
218        DenseMatrix vecs = new DenseMatrix(A.getArray());
219        DenseVector W = new DenseVector(dim);
220        
221        sygvd(1, "V", "U", vecs, new DenseMatrix(B.getArray()), W);
222        
223        Matrix evecs = new Matrix(dim, numVecs);
224        final double[][] evecsData = evecs.getArray();
225        final double[] vecsData = vecs.getData();
226        final double[] valsData = W.getData();
227        
228        int [] indices = ArrayUtils.range(0, valsData.length-1);
229        ArrayUtils.parallelQuicksortDescending(valsData, indices);
230        
231        for (int r=0; r<dim; r++)
232                for (int c=0; c<numVecs; c++)
233                        evecsData[r][c] = vecsData[r + indices[c] * dim];
234        
235        return new IndependentPair<Matrix, double[]>(evecs, valsData);
236    }
237}