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 java.util.Arrays;
26
27 import org.hipparchus.exception.LocalizedCoreFormats;
28 import org.hipparchus.exception.MathIllegalArgumentException;
29 import org.hipparchus.util.FastMath;
30
31
32 /**
33 * Class transforming a symmetrical matrix to tridiagonal shape.
34 * <p>A symmetrical m × m matrix A can be written as the product of three matrices:
35 * A = Q × T × Q<sup>T</sup> with Q an orthogonal matrix and T a symmetrical
36 * tridiagonal matrix. Both Q and T are m × m matrices.</p>
37 * <p>This implementation only uses the upper part of the matrix, the part below the
38 * diagonal is not accessed at all.</p>
39 * <p>Transformation to tridiagonal shape is often not a goal by itself, but it is
40 * an intermediate step in more general decomposition algorithms like {@link
41 * EigenDecompositionSymmetric eigen decomposition}. This class is therefore intended for internal
42 * use by the library and is not public. As a consequence of this explicitly limited scope,
43 * many methods directly returns references to internal arrays, not copies.</p>
44 */
45 class TriDiagonalTransformer {
46 /** Householder vectors. */
47 private final double[][] householderVectors;
48 /** Main diagonal. */
49 private final double[] main;
50 /** Secondary diagonal. */
51 private final double[] secondary;
52 /** Cached value of Q. */
53 private RealMatrix cachedQ;
54 /** Cached value of Qt. */
55 private RealMatrix cachedQt;
56 /** Cached value of T. */
57 private RealMatrix cachedT;
58
59 /**
60 * Build the transformation to tridiagonal shape of a symmetrical matrix.
61 * <p>The specified matrix is assumed to be symmetrical without any check.
62 * Only the upper triangular part of the matrix is used.</p>
63 *
64 * @param matrix Symmetrical matrix to transform.
65 * @throws MathIllegalArgumentException if the matrix is not square.
66 */
67 TriDiagonalTransformer(RealMatrix matrix) {
68 if (!matrix.isSquare()) {
69 throw new MathIllegalArgumentException(LocalizedCoreFormats.NON_SQUARE_MATRIX,
70 matrix.getRowDimension(), matrix.getColumnDimension());
71 }
72
73 final int m = matrix.getRowDimension();
74 householderVectors = matrix.getData();
75 main = new double[m];
76 secondary = new double[m - 1];
77 cachedQ = null;
78 cachedQt = null;
79 cachedT = null;
80
81 // transform matrix
82 transform();
83 }
84
85 /**
86 * Returns the matrix Q of the transform.
87 * <p>Q is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
88 * @return the Q matrix
89 */
90 public RealMatrix getQ() {
91 if (cachedQ == null) {
92 cachedQ = getQT().transpose();
93 }
94 return cachedQ;
95 }
96
97 /**
98 * Returns the transpose of the matrix Q of the transform.
99 * <p>Q is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
100 * @return the Q matrix
101 */
102 public RealMatrix getQT() {
103 if (cachedQt == null) {
104 final int m = householderVectors.length;
105 double[][] qta = new double[m][m];
106
107 // build up first part of the matrix by applying Householder transforms
108 for (int k = m - 1; k >= 1; --k) {
109 final double[] hK = householderVectors[k - 1];
110 qta[k][k] = 1;
111 if (hK[k] != 0.0) {
112 final double inv = 1.0 / (secondary[k - 1] * hK[k]);
113 double beta = 1.0 / secondary[k - 1];
114 qta[k][k] = 1 + beta * hK[k];
115 for (int i = k + 1; i < m; ++i) {
116 qta[k][i] = beta * hK[i];
117 }
118 for (int j = k + 1; j < m; ++j) {
119 beta = 0;
120 for (int i = k + 1; i < m; ++i) {
121 beta += qta[j][i] * hK[i];
122 }
123 beta *= inv;
124 qta[j][k] = beta * hK[k];
125 for (int i = k + 1; i < m; ++i) {
126 qta[j][i] += beta * hK[i];
127 }
128 }
129 }
130 }
131 qta[0][0] = 1;
132 cachedQt = MatrixUtils.createRealMatrix(qta);
133 }
134
135 // return the cached matrix
136 return cachedQt;
137 }
138
139 /**
140 * Returns the tridiagonal matrix T of the transform.
141 * @return the T matrix
142 */
143 public RealMatrix getT() {
144 if (cachedT == null) {
145 final int m = main.length;
146 double[][] ta = new double[m][m];
147 for (int i = 0; i < m; ++i) {
148 ta[i][i] = main[i];
149 if (i > 0) {
150 ta[i][i - 1] = secondary[i - 1];
151 }
152 if (i < main.length - 1) {
153 ta[i][i + 1] = secondary[i];
154 }
155 }
156 cachedT = MatrixUtils.createRealMatrix(ta);
157 }
158
159 // return the cached matrix
160 return cachedT;
161 }
162
163 /**
164 * Get the Householder vectors of the transform.
165 * <p>Note that since this class is only intended for internal use,
166 * it returns directly a reference to its internal arrays, not a copy.</p>
167 * @return the main diagonal elements of the B matrix
168 */
169 double[][] getHouseholderVectorsRef() {
170 return householderVectors; // NOPMD - returning an internal array is intentional and documented here
171 }
172
173 /**
174 * Get the main diagonal elements of the matrix T of the transform.
175 * <p>Note that since this class is only intended for internal use,
176 * it returns directly a reference to its internal arrays, not a copy.</p>
177 * @return the main diagonal elements of the T matrix
178 */
179 double[] getMainDiagonalRef() {
180 return main; // NOPMD - returning an internal array is intentional and documented here
181 }
182
183 /**
184 * Get the secondary diagonal elements of the matrix T of the transform.
185 * <p>Note that since this class is only intended for internal use,
186 * it returns directly a reference to its internal arrays, not a copy.</p>
187 * @return the secondary diagonal elements of the T matrix
188 */
189 double[] getSecondaryDiagonalRef() {
190 return secondary; // NOPMD - returning an internal array is intentional and documented here
191 }
192
193 /**
194 * Transform original matrix to tridiagonal form.
195 * <p>Transformation is done using Householder transforms.</p>
196 */
197 private void transform() {
198 final int m = householderVectors.length;
199 final double[] z = new double[m];
200 for (int k = 0; k < m - 1; k++) {
201
202 //zero-out a row and a column simultaneously
203 final double[] hK = householderVectors[k];
204 main[k] = hK[k];
205 double xNormSqr = 0;
206 for (int j = k + 1; j < m; ++j) {
207 final double c = hK[j];
208 xNormSqr += c * c;
209 }
210 final double a = (hK[k + 1] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
211 secondary[k] = a;
212 if (a != 0.0) {
213 // apply Householder transform from left and right simultaneously
214
215 hK[k + 1] -= a;
216 final double beta = -1 / (a * hK[k + 1]);
217
218 // compute a = beta A v, where v is the Householder vector
219 // this loop is written in such a way
220 // 1) only the upper triangular part of the matrix is accessed
221 // 2) access is cache-friendly for a matrix stored in rows
222 Arrays.fill(z, k + 1, m, 0);
223 for (int i = k + 1; i < m; ++i) {
224 final double[] hI = householderVectors[i];
225 final double hKI = hK[i];
226 double zI = hI[i] * hKI;
227 for (int j = i + 1; j < m; ++j) {
228 final double hIJ = hI[j];
229 zI += hIJ * hK[j];
230 z[j] += hIJ * hKI;
231 }
232 z[i] = beta * (z[i] + zI);
233 }
234
235 // compute gamma = beta vT z / 2
236 double gamma = 0;
237 for (int i = k + 1; i < m; ++i) {
238 gamma += z[i] * hK[i];
239 }
240 gamma *= beta / 2;
241
242 // compute z = z - gamma v
243 for (int i = k + 1; i < m; ++i) {
244 z[i] -= gamma * hK[i];
245 }
246
247 // update matrix: A = A - v zT - z vT
248 // only the upper triangular part of the matrix is updated
249 for (int i = k + 1; i < m; ++i) {
250 final double[] hI = householderVectors[i];
251 for (int j = i; j < m; ++j) {
252 hI[j] -= hK[i] * z[j] + z[i] * hK[j];
253 }
254 }
255 }
256 }
257 main[m - 1] = householderVectors[m - 1][m - 1];
258 }
259 }