1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 /*
19 * This is not the original file distributed by the Apache Software Foundation
20 * It has been modified by the Hipparchus project
21 */
22
23 package org.hipparchus.linear;
24
25 import org.hipparchus.exception.LocalizedCoreFormats;
26 import org.hipparchus.exception.MathIllegalArgumentException;
27 import org.hipparchus.util.FastMath;
28 import org.hipparchus.util.Precision;
29
30 /**
31 * Class transforming a general real matrix to Hessenberg form.
32 * <p>A m × m matrix A can be written as the product of three matrices: A = P
33 * × H × P<sup>T</sup> with P an orthogonal matrix and H a Hessenberg
34 * matrix. Both P and H are m × m matrices.</p>
35 * <p>Transformation to Hessenberg form is often not a goal by itself, but it is an
36 * intermediate step in more general decomposition algorithms like
37 * {@link EigenDecompositionSymmetric eigen decomposition}. This class is therefore
38 * intended for internal use by the library and is not public. As a consequence
39 * of this explicitly limited scope, many methods directly returns references to
40 * internal arrays, not copies.</p>
41 * <p>This class is based on the method orthes in class EigenvalueDecomposition
42 * from the <a href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library.</p>
43 *
44 * @see <a href="http://mathworld.wolfram.com/HessenbergDecomposition.html">MathWorld</a>
45 * @see <a href="http://en.wikipedia.org/wiki/Householder_transformation">Householder Transformations</a>
46 */
47 public class HessenbergTransformer {
48 /** Householder vectors. */
49 private final double[][] householderVectors;
50 /** Temporary storage vector. */
51 private final double[] ort;
52 /** Cached value of P. */
53 private RealMatrix cachedP;
54 /** Cached value of Pt. */
55 private RealMatrix cachedPt;
56 /** Cached value of H. */
57 private RealMatrix cachedH;
58
59 /**
60 * Build the transformation to Hessenberg form of a general matrix.
61 *
62 * @param matrix matrix to transform
63 * @throws MathIllegalArgumentException if the matrix is not square
64 */
65 public HessenbergTransformer(final RealMatrix matrix) {
66 if (!matrix.isSquare()) {
67 throw new MathIllegalArgumentException(LocalizedCoreFormats.NON_SQUARE_MATRIX,
68 matrix.getRowDimension(), matrix.getColumnDimension());
69 }
70
71 final int m = matrix.getRowDimension();
72 householderVectors = matrix.getData();
73 ort = new double[m];
74 cachedP = null;
75 cachedPt = null;
76 cachedH = null;
77
78 // transform matrix
79 transform();
80 }
81
82 /**
83 * Returns the matrix P of the transform.
84 * <p>P is an orthogonal matrix, i.e. its inverse is also its transpose.</p>
85 *
86 * @return the P matrix
87 */
88 public RealMatrix getP() {
89 if (cachedP == null) {
90 final int n = householderVectors.length;
91 final int high = n - 1;
92 final double[][] pa = new double[n][n];
93
94 for (int i = 0; i < n; i++) {
95 for (int j = 0; j < n; j++) {
96 pa[i][j] = (i == j) ? 1 : 0;
97 }
98 }
99
100 for (int m = high - 1; m >= 1; m--) {
101 if (householderVectors[m][m - 1] != 0.0) {
102 for (int i = m + 1; i <= high; i++) {
103 ort[i] = householderVectors[i][m - 1];
104 }
105
106 for (int j = m; j <= high; j++) {
107 double g = 0.0;
108
109 for (int i = m; i <= high; i++) {
110 g += ort[i] * pa[i][j];
111 }
112
113 // Double division avoids possible underflow
114 g = (g / ort[m]) / householderVectors[m][m - 1];
115
116 for (int i = m; i <= high; i++) {
117 pa[i][j] += g * ort[i];
118 }
119 }
120 }
121 }
122
123 cachedP = MatrixUtils.createRealMatrix(pa);
124 }
125 return cachedP;
126 }
127
128 /**
129 * Returns the transpose of the matrix P of the transform.
130 * <p>P is an orthogonal matrix, i.e. its inverse is also its transpose.</p>
131 *
132 * @return the transpose of the P matrix
133 */
134 public RealMatrix getPT() {
135 if (cachedPt == null) {
136 cachedPt = getP().transpose();
137 }
138
139 // return the cached matrix
140 return cachedPt;
141 }
142
143 /**
144 * Returns the Hessenberg matrix H of the transform.
145 *
146 * @return the H matrix
147 */
148 public RealMatrix getH() {
149 if (cachedH == null) {
150 final int m = householderVectors.length;
151 final double[][] h = new double[m][m];
152 for (int i = 0; i < m; ++i) {
153 if (i > 0) {
154 // copy the entry of the lower sub-diagonal
155 h[i][i - 1] = householderVectors[i][i - 1];
156 }
157
158 // copy upper triangular part of the matrix
159 System.arraycopy(householderVectors[i], i, h[i], i, m - i);
160 }
161 cachedH = MatrixUtils.createRealMatrix(h);
162 }
163
164 // return the cached matrix
165 return cachedH;
166 }
167
168 /**
169 * Get the Householder vectors of the transform.
170 * <p>Note that since this class is only intended for internal use, it returns
171 * directly a reference to its internal arrays, not a copy.</p>
172 *
173 * @return the main diagonal elements of the B matrix
174 */
175 double[][] getHouseholderVectorsRef() {
176 return householderVectors; // NOPMD - returning an internal array is intentional and documented here
177 }
178
179 /**
180 * Transform original matrix to Hessenberg form.
181 * <p>Transformation is done using Householder transforms.</p>
182 */
183 private void transform() {
184 final int n = householderVectors.length;
185 final int high = n - 1;
186
187 for (int m = 1; m <= high - 1; m++) {
188 // Scale column.
189 double scale = 0;
190 for (int i = m; i <= high; i++) {
191 scale += FastMath.abs(householderVectors[i][m - 1]);
192 }
193
194 if (!Precision.equals(scale, 0)) {
195 // Compute Householder transformation.
196 double h = 0;
197 for (int i = high; i >= m; i--) {
198 ort[i] = householderVectors[i][m - 1] / scale;
199 h += ort[i] * ort[i];
200 }
201 final double g = (ort[m] > 0) ? -FastMath.sqrt(h) : FastMath.sqrt(h);
202
203 h -= ort[m] * g;
204 ort[m] -= g;
205
206 // Apply Householder similarity transformation
207 // H = (I - u*u' / h) * H * (I - u*u' / h)
208
209 for (int j = m; j < n; j++) {
210 double f = 0;
211 for (int i = high; i >= m; i--) {
212 f += ort[i] * householderVectors[i][j];
213 }
214 f /= h;
215 for (int i = m; i <= high; i++) {
216 householderVectors[i][j] -= f * ort[i];
217 }
218 }
219
220 for (int i = 0; i <= high; i++) {
221 double f = 0;
222 for (int j = high; j >= m; j--) {
223 f += ort[j] * householderVectors[i][j];
224 }
225 f /= h;
226 for (int j = m; j <= high; j++) {
227 householderVectors[i][j] -= f * ort[j];
228 }
229 }
230
231 ort[m] = scale * ort[m];
232 householderVectors[m][m - 1] = scale * g;
233 }
234 }
235 }
236 }