1 /*
2     Genome-wide Efficient Mixed Model Association (GEMMA)
3     Copyright (C) 2011-2017 Xiang Zhou
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "gsl/gsl_linalg.h"
20 #include "gsl/gsl_matrix.h"
21 #include "gsl/gsl_vector.h"
22 #include "gsl/gsl_sys.h" // for gsl_isnan, gsl_isinf, gsl_isfinite
23 #include <cmath>
24 #include <iostream>
25 #include <vector>
26 
27 #include "debug.h"
28 #include "lapack.h"
29 #include "mathfunc.h"
30 
31 using namespace std;
32 
33 extern "C" void dgemm_(char *TRANSA, char *TRANSB, int *M, int *N, int *K,
34                        double *ALPHA, double *A, int *LDA, double *B, int *LDB,
35                        double *BETA, double *C, int *LDC);
36 extern "C" void dpotrf_(char *UPLO, int *N, double *A, int *LDA, int *INFO);
37 extern "C" void dpotrs_(char *UPLO, int *N, int *NRHS, double *A, int *LDA,
38                         double *B, int *LDB, int *INFO);
39 extern "C" void dsyev_(char *JOBZ, char *UPLO, int *N, double *A, int *LDA,
40                        double *W, double *WORK, int *LWORK, int *INFO);
41 extern "C" void dsyevr_(char *JOBZ, char *RANGE, char *UPLO, int *N, double *A,
42                         int *LDA, double *VL, double *VU, int *IL, int *IU,
43                         double *ABSTOL, int *M, double *W, double *Z, int *LDZ,
44                         int *ISUPPZ, double *WORK, int *LWORK, int *IWORK,
45                         int *LIWORK, int *INFO);
46 extern "C" double ddot_(int *N, double *DX, int *INCX, double *DY, int *INCY);
47 
48 // Cholesky decomposition, A is destroyed.
lapack_cholesky_decomp(gsl_matrix * A)49 void lapack_cholesky_decomp(gsl_matrix *A) {
50   int N = A->size1, LDA = A->size1, INFO;
51   char UPLO = 'L';
52 
53   if (N != (int)A->size2) {
54     cout << "Matrix needs to be symmetric and same dimension in "
55          << "lapack_cholesky_decomp." << endl;
56     return;
57   }
58 
59   dpotrf_(&UPLO, &N, A->data, &LDA, &INFO);
60   if (INFO != 0) {
61     cout << "Cholesky decomposition unsuccessful in "
62          << "lapack_cholesky_decomp." << endl;
63     return;
64   }
65 
66   return;
67 }
68 
69 // Cholesky solve, A is decomposed.
lapack_cholesky_solve(gsl_matrix * A,const gsl_vector * b,gsl_vector * x)70 void lapack_cholesky_solve(gsl_matrix *A, const gsl_vector *b, gsl_vector *x) {
71   int N = A->size1, NRHS = 1, LDA = A->size1, LDB = b->size, INFO;
72   char UPLO = 'L';
73 
74   if (N != (int)A->size2 || N != LDB) {
75     cout << "Matrix needs to be symmetric and same dimension in "
76          << "lapack_cholesky_solve." << endl;
77     return;
78   }
79 
80   gsl_vector_memcpy(x, b);
81   dpotrs_(&UPLO, &N, &NRHS, A->data, &LDA, x->data, &LDB, &INFO);
82   if (INFO != 0) {
83     cout << "Cholesky solve unsuccessful in lapack_cholesky_solve." << endl;
84     return;
85   }
86 
87   return;
88 }
89 
lapack_dgemm(char * TransA,char * TransB,double alpha,const gsl_matrix * A,const gsl_matrix * B,double beta,gsl_matrix * C)90 void lapack_dgemm(char *TransA, char *TransB, double alpha, const gsl_matrix *A,
91                   const gsl_matrix *B, double beta, gsl_matrix *C) {
92   int M, N, K1, K2, LDA = A->size1, LDB = B->size1, LDC = C->size2;
93 
94   if (*TransA == 'N' || *TransA == 'n') {
95     M = A->size1;
96     K1 = A->size2;
97   } else if (*TransA == 'T' || *TransA == 't') {
98     M = A->size2;
99     K1 = A->size1;
100   } else {
101     cout << "need 'N' or 'T' in lapack_dgemm" << endl;
102     return;
103   }
104 
105   if (*TransB == 'N' || *TransB == 'n') {
106     N = B->size2;
107     K2 = B->size1;
108   } else if (*TransB == 'T' || *TransB == 't') {
109     N = B->size1;
110     K2 = B->size2;
111   } else {
112     cout << "need 'N' or 'T' in lapack_dgemm" << endl;
113     return;
114   }
115 
116   if (K1 != K2) {
117     cout << "A and B not compatible in lapack_dgemm" << endl;
118     return;
119   }
120   if (C->size1 != (size_t)M || C->size2 != (size_t)N) {
121     cout << "C not compatible in lapack_dgemm" << endl;
122     return;
123   }
124 
125   gsl_matrix *A_t = gsl_matrix_alloc(A->size2, A->size1);
126   gsl_matrix_transpose_memcpy(A_t, A);
127   gsl_matrix *B_t = gsl_matrix_alloc(B->size2, B->size1);
128   gsl_matrix_transpose_memcpy(B_t, B);
129   gsl_matrix *C_t = gsl_matrix_alloc(C->size2, C->size1);
130   gsl_matrix_transpose_memcpy(C_t, C);
131 
132   check_int_mult_overflow(M,K1);
133   check_int_mult_overflow(N,K1);
134   check_int_mult_overflow(M,N);
135 
136   dgemm_(TransA, TransB, &M, &N, &K1, &alpha, A_t->data, &LDA, B_t->data, &LDB,
137          &beta, C_t->data, &LDC);
138 
139   gsl_matrix_transpose_memcpy(C, C_t);
140 
141   gsl_matrix_free(A_t);
142   gsl_matrix_free(B_t);
143   gsl_matrix_free(C_t);
144   return;
145 }
146 
147 // Eigenvalue decomposition, matrix A is destroyed. Returns eigenvalues in
148 // 'eval'. Also returns matrix 'evec' (U).
lapack_eigen_symmv(gsl_matrix * A,gsl_vector * eval,gsl_matrix * evec,const size_t flag_largematrix)149 void lapack_eigen_symmv(gsl_matrix *A, gsl_vector *eval, gsl_matrix *evec,
150                         const size_t flag_largematrix) {
151   if (flag_largematrix == 1) { // not sure this flag is used!
152     int N = A->size1, LDA = A->size1, INFO, LWORK = -1;
153     char JOBZ = 'V', UPLO = 'L';
154 
155     if (N != (int)A->size2 || N != (int)eval->size) {
156       cout << "Matrix needs to be symmetric and same "
157            << "dimension in lapack_eigen_symmv." << endl;
158       return;
159     }
160 
161     LWORK = 3 * N;
162     double *WORK = new double[LWORK];
163     dsyev_(&JOBZ, &UPLO, &N, A->data, &LDA, eval->data, WORK, &LWORK, &INFO);
164     if (INFO != 0) {
165       cout << "Eigen decomposition unsuccessful in "
166            << "lapack_eigen_symmv." << endl;
167       return;
168     }
169 
170     gsl_matrix_view A_sub = gsl_matrix_submatrix(A, 0, 0, N, N);
171     gsl_matrix_memcpy(evec, &A_sub.matrix);
172     gsl_matrix_transpose(evec);
173 
174     delete[] WORK;
175   } else {
176     // entering here
177     int N = A->size1, LDA = A->size1, LDZ = A->size1, INFO;
178     int LWORK = -1, LIWORK = -1;
179     char JOBZ = 'V', UPLO = 'L', RANGE = 'A';
180     double ABSTOL = 1.0E-7;
181 
182     // VL, VU, IL, IU are not referenced; M equals N if RANGE='A'.
183     double VL = 0.0, VU = 0.0;
184     int IL = 0, IU = 0, M;
185 
186     if (N != (int)A->size2 || N != (int)eval->size) {
187       cout << "Matrix needs to be symmetric and same "
188            << "dimension in lapack_eigen_symmv." << endl;
189       return;
190     }
191 
192     int *ISUPPZ = new int[2 * N];
193 
194     double WORK_temp[1];
195     int IWORK_temp[1];
196 
197     // disable fast NaN checking for now - dsyevr throws NaN errors,
198     // but fixes them (apparently)
199     if (is_check_mode()) disable_segfpe();
200 
201     // DSYEVR - computes selected eigenvalues and, optionally,
202     // eigenvectors of a real symmetric matrix
203     // Here compute both (JOBZ is V), all eigenvalues (RANGE is A)
204     // Lower triangle is stored (UPLO is L)
205     dsyevr_(&JOBZ, &RANGE, &UPLO, &N, A->data, &LDA, &VL, &VU, &IL, &IU,
206             &ABSTOL, &M, eval->data, evec->data, &LDZ, ISUPPZ, WORK_temp,
207             &LWORK, IWORK_temp, &LIWORK, &INFO);
208     // If info = 0, the execution is successful.
209     // If info = -i, the i-th parameter had an illegal value.
210     // If info = i, an internal error has occurred.
211 
212     if (INFO != 0) cerr << "ERROR: value of INFO is " << INFO;
213     enforce_msg(INFO == 0, "lapack_eigen_symmv failed");
214     LWORK = (int)WORK_temp[0];    // The dimension of the array work.
215     LIWORK = (int)IWORK_temp[0];  // The dimension of the array iwork, lwork≥ max(1, 10n).
216 
217     double *WORK = new double[LWORK];
218     int *IWORK = new int[LIWORK];
219 
220     dsyevr_(&JOBZ, &RANGE, &UPLO, &N, A->data, &LDA, &VL, &VU, &IL, &IU,
221             &ABSTOL, &M, eval->data, evec->data, &LDZ, ISUPPZ, WORK, &LWORK,
222             IWORK, &LIWORK, &INFO);
223     if (INFO != 0) cerr << "ERROR: value of INFO is " << INFO;
224     enforce_msg(INFO == 0, "lapack_eigen_symmv failed");
225 
226     if (is_check_mode()) enable_segfpe(); // reinstate fast NaN checking
227 
228     gsl_matrix_transpose(evec);
229 
230     delete[] ISUPPZ;
231     delete[] WORK;
232     delete[] IWORK;
233   }
234 
235   return;
236 }
237 
238 // Does NOT set eigenvalues to be positive. G gets destroyed. Returns
239 // eigen trace and values in U and eval (eigenvalues).
EigenDecomp(gsl_matrix * G,gsl_matrix * U,gsl_vector * eval,const size_t flag_largematrix)240 double EigenDecomp(gsl_matrix *G, gsl_matrix *U, gsl_vector *eval,
241                    const size_t flag_largematrix) {
242   lapack_eigen_symmv(G, eval, U, flag_largematrix);
243   assert(!has_nan(eval));
244   // write(eval,"eval");
245 
246   // Calculate track_G=mean(diag(G)).
247   double d = 0.0;
248   for (size_t i = 0; i < eval->size; ++i)
249     d += gsl_vector_get(eval, i);
250 
251   d /= (double)eval->size;
252 
253   return d;
254 }
255 
256 // Does NOT set eigenvalues to be positive. G gets destroyed. Returns
257 // eigen trace and values in U and eval (eigenvalues).  Same as
258 // EigenDecomp but zeroes eigenvalues close to zero. When negative
259 // eigenvalues remain a warning is issued.
EigenDecomp_Zeroed(gsl_matrix * G,gsl_matrix * U,gsl_vector * eval,const size_t flag_largematrix)260 double EigenDecomp_Zeroed(gsl_matrix *G, gsl_matrix *U, gsl_vector *eval,
261                           const size_t flag_largematrix) {
262   EigenDecomp(G,U,eval,flag_largematrix);
263   auto d = 0.0;
264   int count_zero_eigenvalues = 0;
265   int count_negative_eigenvalues = 0;
266   for (size_t i = 0; i < eval->size; i++) {
267     // if (std::abs(gsl_vector_get(eval, i)) < EIGEN_MINVALUE)
268     if (gsl_vector_get(eval, i) < 1e-10)
269       gsl_vector_set(eval, i, 0.0);
270     // checks
271     if (gsl_vector_get(eval,i) == 0.0)
272       count_zero_eigenvalues += 1;
273     if (gsl_vector_get(eval,i) < -EIGEN_MINVALUE) // count smaller than -EIGEN_MINVALUE
274       count_negative_eigenvalues += 1;
275     d += gsl_vector_get(eval, i);
276   }
277   d /= (double)eval->size;
278   if (count_zero_eigenvalues > 1) {
279     write(eval,"eigenvalues");
280     std::string msg = "Matrix G has ";
281     msg += std::to_string(count_zero_eigenvalues);
282     msg += " eigenvalues close to zero";
283     warning_msg(msg);
284   }
285   const bool negative_eigen_values = has_negative_values_but_one(eval);
286   if (negative_eigen_values) {
287     write(eval,"eigenvalues");
288     warning_msg("K has more than one negative eigenvalues!");
289   }
290   return d;
291 }
292 
CholeskySolve(gsl_matrix * Omega,gsl_vector * Xty,gsl_vector * OiXty)293 double CholeskySolve(gsl_matrix *Omega, gsl_vector *Xty, gsl_vector *OiXty) {
294   double logdet_O = 0.0;
295 
296   lapack_cholesky_decomp(Omega);
297   for (size_t i = 0; i < Omega->size1; ++i) {
298     logdet_O += log(gsl_matrix_get(Omega, i, i));
299   }
300   logdet_O *= 2.0;
301   lapack_cholesky_solve(Omega, Xty, OiXty);
302 
303   return logdet_O;
304 }
305 
306 // LU decomposition.
LUDecomp(gsl_matrix * LU,gsl_permutation * p,int * signum)307 void LUDecomp(gsl_matrix *LU, gsl_permutation *p, int *signum) {
308   // debug_msg("entering");
309   enforce_gsl(gsl_linalg_LU_decomp(LU, p, signum));
310   return;
311 }
312 
313 // LU invert. Returns inverse. Note that GSL does not recommend using
314 // this function
315 
316 // These functions compute the inverse of a matrix A from its LU
317 // decomposition (LU,p), storing the result in the matrix inverse. The
318 // inverse is computed by solving the system A x = b for each column
319 // of the identity matrix. It is preferable to avoid direct use of the
320 // inverse whenever possible, as the linear solver functions can
321 // obtain the same result more efficiently and reliably (consult any
322 // introductory textbook on numerical linear algebra for details).
LUInvert(const gsl_matrix * LU,const gsl_permutation * p,gsl_matrix * ret_inverse)323 void LUInvert(const gsl_matrix *LU, const gsl_permutation *p, gsl_matrix *ret_inverse) {
324   // debug_msg("entering");
325   if (is_check_mode())
326     LULndet(LU);
327 
328   enforce_gsl(gsl_linalg_LU_invert(LU, p, ret_inverse));
329 }
330 
331 // LU lndet.
332 
333 // These functions compute the logarithm of the absolute value of the
334 // determinant of a matrix A, \ln|\det(A)|, from its LU decomposition,
335 // LU. This function may be useful if the direct computation of the
336 // determinant would overflow or underflow.
337 
LULndet(const gsl_matrix * LU)338 double LULndet(const gsl_matrix *LU) {
339   // debug_msg("entering");
340 
341   double res = gsl_linalg_LU_lndet((gsl_matrix *)LU);
342   enforce_msg(!is_inf(res), "LU determinant is zero -> LU is not invertable");
343   return res;
344 }
345 
346 // LU solve.
LUSolve(const gsl_matrix * LU,const gsl_permutation * p,const gsl_vector * b,gsl_vector * x)347 void LUSolve(const gsl_matrix *LU, const gsl_permutation *p,
348              const gsl_vector *b, gsl_vector *x) {
349   // debug_msg("entering");
350   enforce_gsl(gsl_linalg_LU_solve(LU, p, b, x));
351   return;
352 }
353 
lapack_ddot(vector<double> & x,vector<double> & y,double & v)354 bool lapack_ddot(vector<double> &x, vector<double> &y, double &v) {
355   bool flag = false;
356   int incx = 1;
357   int incy = 1;
358   int n = (int)x.size();
359   if (x.size() == y.size()) {
360     v = ddot_(&n, &x[0], &incx, &y[0], &incy);
361     flag = true;
362   }
363 
364   return flag;
365 }
366