You are viewing an archived page from the original site. Go to the new numericalmethod.com →

source: public/Misc/HouseholderReflection.java @ 207

Revision 3, 9.8 KB checked in by haksunli, 4 years ago (diff)

ADD: copied the whole 'public svn' over from numericalmethod.info machine to this machine

Line 
1/*
2 * Copyright (c) Numerical Method Inc.
3 * http://www.numericalmethod.com/
4 *
5 * THIS SOFTWARE IS LICENSED, NOT SOLD.
6 *
7 * YOU MAY USE THIS SOFTWARE ONLY AS DESCRIBED IN THE LICENSE.
8 * IF YOU ARE NOT AWARE OF AND/OR DO NOT AGREE TO THE TERMS OF THE LICENSE,
9 * DO NOT USE THIS SOFTWARE.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS", WITH NO WARRANTY WHATSOEVER,
12 * EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
13 * ANY WARRANTIES OF ACCURACY, ACCESSIBILITY, COMPLETENESS,
14 * FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, NON-INFRINGEMENT,
15 * TITLE AND USEFULNESS.
16 *
17 * IN NO EVENT AND UNDER NO LEGAL THEORY,
18 * WHETHER IN ACTION, CONTRACT, NEGLIGENCE, TORT, OR OTHERWISE,
19 * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20 * ANY CLAIMS, DAMAGES OR OTHER LIABILITIES,
21 * ARISING AS A RESULT OF USING OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23package com.numericalmethod.suanshu.matrix.doubles.dense.operation.factorization;
24
25import com.numericalmethod.suanshu.vector.doubles.Vector;
26import com.numericalmethod.suanshu.matrix.doubles.Matrix;
27import com.numericalmethod.suanshu.matrix.doubles.dense.DenseMatrix;
28import com.numericalmethod.suanshu.matrix.doubles.dense.PermutationMatrix;
29import com.numericalmethod.suanshu.matrix.doubles.dense.UpperTriangularMatrix;
30import com.numericalmethod.suanshu.matrix.doubles.dense.operation.CreateMatrix;
31import com.numericalmethod.suanshu.matrix.doubles.dense.operation.Householder;
32import com.numericalmethod.suanshu.matrix.doubles.dense.operation.Householder.Context;
33import com.numericalmethod.suanshu.misc.SuanShuUtils;
34import com.numericalmethod.suanshu.vector.doubles.dense.DenseVector;
35import static com.numericalmethod.suanshu.number.DoubleUtils.*;
36import static com.numericalmethod.suanshu.vector.doubles.dense.operation.CreateVector.*;
37
38/**
39 * Successive Householder reflections gradually transform a matrix <code>A</code> to the upper triangular form.
40 *
41 * <p>
42 * For example, the first step is to multiply <code>A</code> with the Householder matrix <code>Q<sub>1</sub></code> so that
43 * matrix <code>Q1 %*% A</code> has zeros in the left column (except for the first row).
44 * That is,
45 *
46 * <blockquote><code><pre>
47 *             | a1 * ... * |
48 *             | 0          |
49 * Q1 %*% A =  | .          |
50 *             | .     A'   |
51 *             | .          |
52 *             | 0          |
53 * </pre></code></blockquote>
54 *
55 * At the end,
56 * <blockquote><code><pre>
57 * Qn %*% ... %*% Q1 %*% A = R
58 * </pre></code></blockquote>
59 * where <code>R</code> is upper triangular.
60 *
61 * <p>
62 * Householder reflection has a better numerical stability than the Gram-Schmidt process.
63 *
64 * @author Haksun Li
65 *
66 * @see <a href="http://en.wikipedia.org/wiki/QR_decomposition#Using_Householder_reflections">Wikipedia: Using Householder reflections</a>
67 */
68public class HouseholderReflection implements QRDecomposition {
69
70    /**
71     * number of rows
72     */
73    public final int nrows;
74    /**
75     * number of columns
76     */
77    public final int ncols;
78    /**
79     * a precision parameter: when a number |x| ≀ ε, it is considered 0
80     */
81    public final double epsilon;
82    private UpperTriangularMatrix R;//nCols x nCols
83    private Vector[] cols;
84    private Householder[] Hs;
85
86    /**
87     * Construct an instance of the Householder reflection process to orthogonalize a matrix.
88     *
89     * @param A a matrix, where #rows >= #columns
90     * @param epsilon a precision parameter: when a number |x| ≀ ε, it is considered 0
91     * @throws IllegalArgumentException if #rows < #columns
92     */
93    public HouseholderReflection(Matrix A, double epsilon) {
94        SuanShuUtils.assertArgument((A.nRows() >= A.nCols()),
95                "QR decomposition by Householder Reflection applies to matrix where number of rows > number of columns");
96
97        this.nrows = A.nRows();
98        this.ncols = A.nCols();
99        this.epsilon = epsilon;
100
101        R = new UpperTriangularMatrix(ncols);
102        R.set(1, 1, 0);//allocate space
103        cols = new Vector[ncols + 1];//0-th element is not used
104        Hs = new Householder[ncols + 1];//0-th element is not used
105
106        /*
107         * Make a copy of A by columns
108         */
109        for (int i = 1; i <= ncols; ++i) {
110            cols[i] = A.column(i);
111        }
112
113        /*
114         * We apply the Householder operator repeatedly on each columns,
115         * hence looping over columns
116         */
117        for (int i = 1; i <= Math.min(ncols, nrows - 1); i++) {//if square, i <= nRows-1; else i <= nCols (b/c nRows > nCols by assumption)
118            /*
119             * We will zero out in this column all entries below the i-th entry.
120             * E.g., for the first column, we want to have this form (a,0,0,..)'.
121             *
122             * We work on a sub-matrix., namely,
123             * A[i:nRows, i:nCols]
124             */
125            Context defn = Householder.getContext(subVector(cols[i], i, nrows));
126            Vector u = defn.generator;
127            /*
128             * Treat the (sub-)column as 0, if the norm is too small.
129             * This is to prevent spurious Householder operation on a "zero" column,
130             * which may produce incorrect result.
131             */
132            if (compare(Math.abs(defn.lambda), epsilon) <= 0) {//linear dependence on previous columns
133                u = new DenseVector(nrows);//ZERO vector
134                Hs[i] = new Householder(u);
135                R.set(i, i, 0);//record R's diagonal
136            } else {//to reduce computation, we perform Householder reflection only for linearly independent columns
137                u = concat(
138                        new DenseVector(i - 1),//ZERO vector
139                        u);
140                Hs[i] = new Householder(u);
141
142                /*
143                 * Apply the householder transformation to the rest of the columns, but only partially (sub-matrix).
144                 * That is, we set only a sub-matrix.
145                 *
146                 * TODO: how to improve performance by doing inner-product (reflect) only on non-zero entries?
147                 */
148                for (int j = i + 1; j <= ncols; ++j) {
149                    cols[j] = Hs[i].reflect(cols[j]);
150                }
151
152                R.set(i, i, defn.lambda);//record R's diagonal
153            }
154        }
155
156        computeR();
157    }
158
159    /**
160     * Construct an instance of the Householder reflection process to orthogonalize a matrix.
161     *
162     * @param A a matrix, where #rows >= #columns
163     */
164    public HouseholderReflection(Matrix A) {
165        this(A, SuanShuUtils.autoEpsilon(DenseMatrix.as(A).to1DArray()));
166    }
167
168    /**
169     * Compute <code>R</code>.
170     */
171    private void computeR() {
172        /*
173         * R's diagonal is already correct by the constructor.
174         * Just copy the upper triangle entries from the Cols,
175         * which are A modified by a sequence of Householder transformations.
176         */
177        for (int i = 2; i <= ncols; ++i) {//loop over columns
178            for (int j = 1; j < i; ++j) {//loop over rows
179                R.set(j, i, cols[i].get(j));
180            }
181        }
182
183        //for square matrix, last column was not worked on to generate diagonal element
184        if (nrows == ncols) {
185            R.set(ncols, ncols, cols[ncols].get(ncols));
186        }
187    }
188
189    /**
190     * Get a copy of the <code>Q</code> matrix in the QR decomposition.
191     *
192     * <blockquote><code><pre>
193     * Qn %*% ... %*% Q3 %*% Q2 %*% Q1 %*% A = R
194     * A = [Qn %*% ... %*% Q3 %*% Q2 %*% Q1]^-1 %*% R
195     * A = [Q1^-1 %*% Q2^-1 %*% ... %*% Qn^-1] %*% R
196     * A = [Q1 %*% Q2 %*% ... %*% Qn] %*% R, by the unitary property of Householder matrix
197     * So, Q = Q1 %*% Q2 %*% ... %*% Qn.
198     * </pre></code></blockquote>
199     *
200     * <p>
201     * To compute <code>Q</code>,
202     * instead of explicitly doing this multiplication, we can improve the performance
203     * by applying <code>Q<sub>i</sub></code>'s repeatedly on an identity matrix.
204     *
205     * <p>
206     * Dimension of <code>Q</code> is <code>nRows x nCols</code>.
207     *
208     * @return a copy of the <code>Q</code> matrix in the QR decomposition
209     */
210    @Override
211    public Matrix Q() {
212        Matrix Q = Householder.product(Hs, 1, ncols, nrows, ncols);//TODO: cache Q
213        return Q;
214    }
215
216    @Override
217    public UpperTriangularMatrix R() {
218        return new UpperTriangularMatrix(R);
219    }
220
221    /**
222     * Get a copy of the <code>P</code> pivoting matrix in the QR decomposition.
223     * <p>
224     * Householder process needs no pivoting.
225     * Hence, <code>P</code> is always an identity matrix.
226     *
227     * @return an identity matrix
228     */
229    @Override
230    public PermutationMatrix P() {
231        return new PermutationMatrix(ncols);
232    }
233
234    /**
235     * Rank is computed by counting the number of non-zero rows in <code>R</code>.
236     *
237     * <p>
238     * <code>Q</code> may have more columns than the rank.
239     * The user is encouraged to interpret the result with caution.
240     *
241     * @return the rank
242     */
243    @Override
244    public int rank() {
245        //count the number of non-zero rows in R
246        //i.e., count the number of non-zero diagonal elements in R
247        int result = 0;
248        for (int i = 1; i <= R.nCols(); ++i) {
249            if (compare(R.get(i, i), 0, epsilon) != 0) {
250                ++result;
251            }
252        }
253        return result;
254    }
255
256    @Override
257    public Matrix squareQ() {
258        Matrix squareQ = Householder.product(Hs, 1, ncols, nrows, nrows);//the dimension is different from that in Q
259        return squareQ;
260    }
261
262    @Override
263    public Matrix tallR() {
264        Matrix tallR = new DenseMatrix(nrows, ncols).ZERO();
265        CreateMatrix.replace(tallR, 1, ncols, 1, ncols, R);
266        return tallR;
267    }
268}
Note: See TracBrowser for help on using the repository browser.