Anasazi Version of the Day
Loading...
Searching...
No Matches
ModalTools.cpp
1// @HEADER
2// *****************************************************************************
3// Anasazi: Block Eigensolvers Package
4//
5// Copyright 2004 NTESS and the Anasazi contributors.
6// SPDX-License-Identifier: BSD-3-Clause
7// *****************************************************************************
8// @HEADER
9
10// This software is a result of the research described in the report
11//
12// "A comparison of algorithms for modal analysis in the absence
13// of a sparse direct method", P. Arbenz, R. Lehoucq, and U. Hetmaniuk,
14// Sandia National Laboratories, Technical report SAND2003-1028J.
15//
16// It is based on the Epetra, AztecOO, and ML packages defined in the Trilinos
17// framework ( http://trilinos.org/ ).
18
19#include "ModalTools.h"
20
21
22ModalTools::ModalTools(const Epetra_Comm &_Comm) :
23 callFortran(),
24 callBLAS(),
25 callLAPACK(),
26 MyComm(_Comm),
27 MyWatch(_Comm),
28 eps(0.0),
29 timeQtMult(0.0),
30 timeQMult(0.0),
31 timeProj_MassMult(0.0),
32 timeNorm_MassMult(0.0),
33 timeProj(0.0),
34 timeNorm(0.0),
35 numProj_MassMult(0),
36 numNorm_MassMult(0) {
37
38 callLAPACK.LAMCH('E', eps);
39
40}
41
42
43int ModalTools::makeSimpleLumpedMass(const Epetra_Operator *M, double *normWeight) const {
44
45 // Return, in the array normWeight, weights value such that
46 // the function Epetra_MultiVector::NormWeighted computes
47 //
48 // || . || = ( N / ( 1^T M 1) )^{1/2} * || . ||_{2}
49 //
50 // where 1 is the vector with unit entries and N is the size of the problem
51 //
52 // normWeight : Array of double (length = # of unknowns on this processor)
53 // Contains at exit the weights
54 //
55 // Note: When M is 0, the function does not do anything
56
57 if (M == 0)
58 return -1;
59
60 int row = (M->OperatorDomainMap()).NumMyElements();
61
62 double *zVal = new double[row];
63
64 Epetra_Vector z(View, M->OperatorDomainMap(), zVal);
65 Epetra_Vector Mz(View, M->OperatorDomainMap(), normWeight);
66
67 z.PutScalar(1.0);
68 M->Apply(z, Mz);
69
70 delete[] zVal;
71
72 int i;
73 double rho = 0.0;
74 for (i = 0; i < row; ++i)
75 rho += normWeight[i];
76
77 normWeight[0] = rho;
78 MyComm.SumAll(normWeight, &rho, 1);
79
80 int info = 0;
81 if (rho <= 0.0) {
82 info = -2;
83 if (MyComm.MyPID() == 0)
84 cerr << "\n !!! The mass matrix gives a negative mass: " << rho << " !!! \n\n";
85 return info;
86 }
87 rho = rho/(M->OperatorDomainMap()).NumGlobalElements();
88
89 //
90 // Note that the definition of the weighted norm in Epetra forces this weight definition
91 // UH 09/03/03
92 //
93
94 rho = sqrt(rho/(M->OperatorDomainMap()).NumGlobalElements());
95
96 for (i = 0; i < row; ++i)
97 normWeight[i] = rho;
98
99 return info;
100
101}
102
103
104int ModalTools::massOrthonormalize(Epetra_MultiVector &X, Epetra_MultiVector &MX,
105 const Epetra_Operator *M, const Epetra_MultiVector &Q,
106 int howMany, int type, double *WS, double kappa) {
107
108 // For the inner product defined by the operator M or the identity (M = 0)
109 // -> Orthogonalize X against Q
110 // -> Orthonormalize X
111 // Modify MX accordingly
112 // WS is used as a workspace (size: (# of columns in X)*(# of rows in X))
113 //
114 // Note that when M is 0, MX is not referenced
115 //
116 // Parameter variables
117 //
118 // X : Vectors to be transformed
119 //
120 // MX : Image of the block vector X by the mass matrix
121 //
122 // Q : Vectors to orthogonalize against
123 //
124 // howMany : Number of vectors of X to orthogonalized
125 // If this number is smaller than the total number of vectors in X,
126 // then it is assumed that the last "howMany" vectors are not orthogonal
127 // while the other vectors in X are othogonal to Q and orthonormal.
128 //
129 // type = 0 (default) > Performs both operations
130 // type = 1 > Performs Q^T M X = 0
131 // type = 2 > Performs X^T M X = I
132 //
133 // WS = Working space (default value = 0)
134 //
135 // kappa= Coefficient determining when to perform a second Gram-Schmidt step
136 // Default value = 1.5625 = (1.25)^2 (as suggested in Parlett's book)
137 //
138 // Output the status of the computation
139 //
140 // info = 0 >> Success.
141 //
142 // info > 0 >> Indicate how many vectors have been tried to avoid rank deficiency for X
143 //
144 // info = -1 >> Failure >> X has zero columns
145 // >> It happens when # col of X > # rows of X
146 // >> It happens when # col of [Q X] > # rows of X
147 // >> It happens when no good random vectors could be found
148
149 int info = 0;
150
151 // Orthogonalize X against Q
152 timeProj -= MyWatch.WallTime();
153 if (type != 2) {
154
155 int xc = howMany;
156 int xr = X.MyLength();
157 int qc = Q.NumVectors();
158
159 Epetra_MultiVector XX(View, X, X.NumVectors()-howMany, howMany);
160
161 Epetra_MultiVector MXX(View, X.Map(), (M) ? MX.Values() + xr*(MX.NumVectors()-howMany)
162 : X.Values() + xr*(X.NumVectors()-howMany),
163 xr, howMany);
164
165 // Perform the Gram-Schmidt transformation for a block of vectors
166
167 // Compute the initial M-norms
168 double *oldDot = new double[xc];
169 MXX.Dot(XX, oldDot);
170
171 // Define the product Q^T * (M*X)
172 double *qTmx = new double[2*qc*xc];
173
174 // Multiply Q' with MX
175 timeQtMult -= MyWatch.WallTime();
176 callBLAS.GEMM('T', 'N', qc, xc, xr, 1.0, Q.Values(), xr, MXX.Values(), xr,
177 0.0, qTmx + qc*xc, qc);
178 MyComm.SumAll(qTmx + qc*xc, qTmx, qc*xc);
179 timeQtMult += MyWatch.WallTime();
180
181 // Multiply by Q and substract the result in X
182 timeQMult -= MyWatch.WallTime();
183 callBLAS.GEMM('N', 'N', xr, xc, qc, -1.0, Q.Values(), xr, qTmx, qc,
184 1.0, XX.Values(), xr);
185 timeQMult += MyWatch.WallTime();
186
187 // Update MX
188 if (M) {
189 if ((qc >= xc) || (WS == 0)) {
190 timeProj_MassMult -= MyWatch.WallTime();
191 M->Apply(XX, MXX);
192 timeProj_MassMult += MyWatch.WallTime();
193 numProj_MassMult += xc;
194 }
195 else {
196 Epetra_MultiVector MQ(View, Q.Map(), WS, Q.MyLength(), qc);
197 timeProj_MassMult -= MyWatch.WallTime();
198 M->Apply(Q, MQ);
199 timeProj_MassMult += MyWatch.WallTime();
200 numProj_MassMult += qc;
201 callBLAS.GEMM('N', 'N', xr, xc, qc, -1.0, MQ.Values(), xr, qTmx, qc,
202 1.0, MXX.Values(), xr);
203 } // if ((qc >= xc) || (WS == 0))
204 } // if (M)
205
206 double newDot = 0.0;
207 int j;
208 for (j = 0; j < xc; ++j) {
209
210 MXX(j)->Dot(*(XX(j)), &newDot);
211
212 if (kappa*newDot < oldDot[j]) {
213
214 // Apply another step of classical Gram-Schmidt
215 timeQtMult -= MyWatch.WallTime();
216 callBLAS.GEMM('T', 'N', qc, xc, xr, 1.0, Q.Values(), xr, MXX.Values(), xr,
217 0.0, qTmx + qc*xc, qc);
218 MyComm.SumAll(qTmx + qc*xc, qTmx, qc*xc);
219 timeQtMult += MyWatch.WallTime();
220
221 timeQMult -= MyWatch.WallTime();
222 callBLAS.GEMM('N', 'N', xr, xc, qc, -1.0, Q.Values(), xr, qTmx, qc,
223 1.0, XX.Values(), xr);
224 timeQMult += MyWatch.WallTime();
225
226 // Update MX
227 if (M) {
228 if ((qc >= xc) || (WS == 0)) {
229 timeProj_MassMult -= MyWatch.WallTime();
230 M->Apply(XX, MXX);
231 timeProj_MassMult += MyWatch.WallTime();
232 numProj_MassMult += xc;
233 }
234 else {
235 Epetra_MultiVector MQ(View, Q.Map(), WS, Q.MyLength(), qc);
236 timeProj_MassMult -= MyWatch.WallTime();
237 M->Apply(Q, MQ);
238 timeProj_MassMult += MyWatch.WallTime();
239 numProj_MassMult += qc;
240 callBLAS.GEMM('N', 'N', xr, xc, qc, -1.0, MQ.Values(), xr, qTmx, qc,
241 1.0, MXX.Values(), xr);
242 } // if ((qc >= xc) || (WS == 0))
243 } // if (M)
244
245 break;
246 } // if (kappa*newDot < oldDot[j])
247 } // for (j = 0; j < xc; ++j)
248
249 delete[] qTmx;
250 delete[] oldDot;
251
252 } // if (type != 2)
253 timeProj += MyWatch.WallTime();
254
255 // Orthonormalize X
256 timeNorm -= MyWatch.WallTime();
257 if (type != 1) {
258
259 int j;
260 int xc = X.NumVectors();
261 int xr = X.MyLength();
262 int globalSize = X.GlobalLength();
263 int shift = (type == 2) ? 0 : Q.NumVectors();
264 int mxc = (M) ? MX.NumVectors() : X.NumVectors();
265
266 bool allocated = false;
267 if (WS == 0) {
268 allocated = true;
269 WS = new double[xr];
270 }
271
272 double *oldMXj = WS;
273 double *MXX = (M) ? MX.Values() : X.Values();
274 double *product = new double[2*xc];
275
276 double dTmp;
277
278 for (j = 0; j < howMany; ++j) {
279
280 int numX = xc - howMany + j;
281 int numMX = mxc - howMany + j;
282
283 // Put zero vectors in X when we are exceeding the space dimension
284 if (numX + shift >= globalSize) {
285 Epetra_Vector XXj(View, X, numX);
286 XXj.PutScalar(0.0);
287 if (M) {
288 Epetra_Vector MXXj(View, MX, numMX);
289 MXXj.PutScalar(0.0);
290 }
291 info = -1;
292 }
293
294 int numTrials;
295 bool rankDef = true;
296 for (numTrials = 0; numTrials < 10; ++numTrials) {
297
298 double *Xj = X.Values() + xr*numX;
299 double *MXj = MXX + xr*numMX;
300
301 double oldDot = 0.0;
302 dTmp = callBLAS.DOT(xr, Xj, MXj);
303 MyComm.SumAll(&dTmp, &oldDot, 1);
304
305 memcpy(oldMXj, MXj, xr*sizeof(double));
306
307 if (numX > 0) {
308
309 // Apply the first Gram-Schmidt
310
311 callBLAS.GEMV('T', xr, numX, 1.0, X.Values(), xr, MXj, 0.0, product + xc);
312 MyComm.SumAll(product + xc, product, numX);
313 callBLAS.GEMV('N', xr, numX, -1.0, X.Values(), xr, product, 1.0, Xj);
314 if (M) {
315 if (xc == mxc) {
316 callBLAS.GEMV('N', xr, numX, -1.0, MXX, xr, product, 1.0, MXj);
317 }
318 else {
319 Epetra_Vector XXj(View, X, numX);
320 Epetra_Vector MXXj(View, MX, numMX);
321 timeNorm_MassMult -= MyWatch.WallTime();
322 M->Apply(XXj, MXXj);
323 timeNorm_MassMult += MyWatch.WallTime();
324 numNorm_MassMult += 1;
325 }
326 }
327
328 double dot = 0.0;
329 dTmp = callBLAS.DOT(xr, Xj, MXj);
330 MyComm.SumAll(&dTmp, &dot, 1);
331
332 if (kappa*dot < oldDot) {
333 callBLAS.GEMV('T', xr, numX, 1.0, X.Values(), xr, MXj, 0.0, product + xc);
334 MyComm.SumAll(product + xc, product, numX);
335 callBLAS.GEMV('N', xr, numX, -1.0, X.Values(), xr, product, 1.0, Xj);
336 if (M) {
337 if (xc == mxc) {
338 callBLAS.GEMV('N', xr, numX, -1.0, MXX, xr, product, 1.0, MXj);
339 }
340 else {
341 Epetra_Vector XXj(View, X, numX);
342 Epetra_Vector MXXj(View, MX, numMX);
343 timeNorm_MassMult -= MyWatch.WallTime();
344 M->Apply(XXj, MXXj);
345 timeNorm_MassMult += MyWatch.WallTime();
346 numNorm_MassMult += 1;
347 }
348 }
349 } // if (kappa*dot < oldDot)
350
351 } // if (numX > 0)
352
353 double norm = 0.0;
354 dTmp = callBLAS.DOT(xr, Xj, oldMXj);
355 MyComm.SumAll(&dTmp, &norm, 1);
356
357 if (norm > oldDot*eps*eps) {
358 norm = 1.0/sqrt(norm);
359 callBLAS.SCAL(xr, norm, Xj);
360 if (M)
361 callBLAS.SCAL(xr, norm, MXj);
362 rankDef = false;
363 break;
364 }
365 else {
366 info += 1;
367 Epetra_Vector XXj(View, X, numX);
368 XXj.Random();
369 Epetra_Vector MXXj(View, MX, numMX);
370 if (M) {
371 timeNorm_MassMult -= MyWatch.WallTime();
372 M->Apply(XXj, MXXj);
373 timeNorm_MassMult += MyWatch.WallTime();
374 numNorm_MassMult += 1;
375 }
376 if (type == 0)
377 massOrthonormalize(XXj, MXXj, M, Q, 1, 1, WS, kappa);
378 } // if (norm > oldDot*eps*eps)
379
380 } // for (numTrials = 0; numTrials < 10; ++numTrials)
381
382 if (rankDef == true) {
383 Epetra_Vector XXj(View, X, numX);
384 XXj.PutScalar(0.0);
385 if (M) {
386 Epetra_Vector MXXj(View, MX, numMX);
387 MXXj.PutScalar(0.0);
388 }
389 info = -1;
390 break;
391 }
392
393 } // for (j = 0; j < howMany; ++j)
394
395 delete[] product;
396
397 if (allocated == true) {
398 delete[] WS;
399 }
400
401 } // if (type != 1)
402 timeNorm += MyWatch.WallTime();
403
404 return info;
405
406}
407
408
409void ModalTools::localProjection(int numRow, int numCol, int dotLength,
410 double *U, int ldU, double *MatV, int ldV,
411 double *UtMatV, int ldUtMatV, double *work) const {
412
413 // This routine forms a projected matrix of a matrix Mat onto the subspace
414 // spanned by U and V
415 //
416 // numRow = Number of columns in U (or number of rows in U^T) (input)
417 //
418 // numCol = Number of columns in V (input)
419 //
420 // dotLength = Local length of vectors U and V (input)
421 //
422 // U = Array of double storing the vectors U (input)
423 //
424 // ldU = Leading dimension in U (input)
425 //
426 // MatV = Array of double storing the vectors Mat*V (input)
427 //
428 // ldMatV = Leading dimension in MatV (input)
429 //
430 // UtMatV = Array of double storing the projected matrix U^T * Mat * V (output)
431 //
432 // ldUtMatV = Leading dimension in UtMatV (input)
433 //
434 // work = Workspace (size >= 2*numRow*numCol)
435
436 int j;
437 int offSet = numRow*numCol;
438
439 callBLAS.GEMM('T', 'N', numRow, numCol, dotLength, 1.0, U, ldU, MatV, ldV,
440 0.0, work + offSet, numRow);
441 MyComm.SumAll(work + offSet, work, offSet);
442
443 double *source = work;
444 double *result = UtMatV;
445 int howMany = numRow*sizeof(double);
446
447 for (j = 0; j < numCol; ++j) {
448 memcpy(result, source, howMany);
449 // Shift the pointers
450 source = source + numRow;
451 result = result + ldUtMatV;
452 }
453
454}
455
456
457int ModalTools::directSolver(int size, double *KK, int ldK, double *MM, int ldM,
458 int &nev, double *EV, int ldEV, double *theta, int verbose, int esType) const {
459
460 // Routine for computing the first NEV generalized eigenpairs of the symmetric pencil (KK, MM)
461 //
462 // Parameter variables:
463 //
464 // size : Size of the matrices KK and MM
465 //
466 // KK : Symmetric "stiffness" matrix (Size = size x ldK)
467 //
468 // ldK : Leading dimension of KK (ldK >= size)
469 //
470 // MM : Symmetric Positive "mass" matrix (Size = size x ldM)
471 //
472 // ldM : Leading dimension of MM (ldM >= size)
473 //
474 // nev : Number of the smallest eigenvalues requested (input)
475 // Number of the smallest computed eigenvalues (output)
476 //
477 // EV : Array to store the eigenvectors (Size = nev x ldEV)
478 //
479 // ldEV : Leading dimension of EV (ldEV >= size)
480 //
481 // theta : Array to store the eigenvalues (Size = nev)
482 //
483 // verbose : Flag to print information on the computation
484 //
485 // esType : Flag to select the algorithm
486 //
487 // esType = 0 (default) Uses LAPACK routine (Cholesky factorization of MM)
488 // with deflation of MM to get orthonormality of
489 // eigenvectors (S^T MM S = I)
490 //
491 // esType = 1 Uses LAPACK routine (Cholesky factorization of MM)
492 // (no check of orthonormality)
493 //
494 // esType = 10 Uses LAPACK routine for simple eigenproblem on KK
495 // (MM is not referenced in this case)
496 //
497 // Note: The code accesses only the upper triangular part of KK and MM.
498 //
499 // Return the integer info on the status of the computation
500 //
501 // info = 0 >> Success
502 //
503 // info = - 20 >> Failure in LAPACK routine
504
505 // Define local arrays
506
507 double *kp = 0;
508 double *mp = 0;
509 double *tt = 0;
510
511 double *U = 0;
512
513 int i, j;
514 int rank = 0;
515 int info = 0;
516 int tmpI;
517
518 int NB = 5 + callFortran.LAENV(1, "dsytrd", "u", size, -1, -1, -1, 6, 1);
519 int lwork = size*NB;
520 double *work = new double[lwork];
521
522// double tol = sqrt(eps);
523 double tol = 1e-12;
524
525 switch (esType) {
526
527 default:
528 case 0:
529
530 // Use the Cholesky factorization of MM to compute the generalized eigenvectors
531
532 // Define storage
533 kp = new double[size*size];
534 mp = new double[size*size];
535 tt = new double[size];
536 U = new double[size*size];
537
538 if (verbose > 4)
539 cout << endl;
540
541 // Copy KK & MM
542 tmpI = sizeof(double);
543 for (rank = size; rank > 0; --rank) {
544 memset(kp, 0, size*size*tmpI);
545 for (i = 0; i < rank; ++i) {
546 memcpy(kp + i*size, KK + i*ldK, (i+1)*tmpI);
547 memcpy(mp + i*size, MM + i*ldM, (i+1)*tmpI);
548 }
549 // Solve the generalized eigenproblem with LAPACK
550 info = 0;
551 callFortran.SYGV(1, 'V', 'U', rank, kp, size, mp, size, tt, work, lwork, &info);
552 // Treat error messages
553 if (info < 0) {
554 if (verbose > 0) {
555 cerr << endl;
556 cerr << " In DSYGV, argument " << -info << "has an illegal value.\n";
557 cerr << endl;
558 }
559 return -20;
560 }
561 if (info > 0) {
562 if (info > rank)
563 rank = info - rank;
564 continue;
565 }
566 // Check the quality of eigenvectors
567 for (i = 0; i < size; ++i) {
568 memcpy(mp + i*size, MM + i*ldM, (i+1)*tmpI);
569 for (j = 0; j < i; ++j)
570 mp[i + j*size] = mp[j + i*size];
571 }
572 callBLAS.GEMM('N', 'N', size, rank, size, 1.0, mp, size, kp, size, 0.0, U, size);
573 callBLAS.GEMM('T', 'N', rank, rank, size, 1.0, kp, size, U, size, 0.0, mp, rank);
574 double maxNorm = 0.0;
575 double maxOrth = 0.0;
576 for (i = 0; i < rank; ++i) {
577 for (j = i; j < rank; ++j) {
578 if (j == i) {
579 maxNorm = (fabs(mp[j+i*rank]-1.0) > maxNorm) ? fabs(mp[j+i*rank]-1.0) : maxNorm;
580 }
581 else {
582 maxOrth = (fabs(mp[j+i*rank]) > maxOrth) ? fabs(mp[j+i*rank]) : maxOrth;
583 }
584 }
585 }
586 if (verbose > 4) {
587 cout << " >> Local eigensolve >> Size: " << rank;
588 cout.precision(2);
589 cout.setf(ios::scientific, ios::floatfield);
590 cout << " Normalization error: " << maxNorm;
591 cout << " Orthogonality error: " << maxOrth;
592 cout << endl;
593 }
594 if ((maxNorm <= tol) && (maxOrth <= tol))
595 break;
596 } // for (rank = size; rank > 0; --rank)
597
598 if (verbose > 4)
599 cout << endl;
600
601 // Copy the eigenvectors
602 memset(EV, 0, nev*ldEV*tmpI);
603 nev = (rank < nev) ? rank : nev;
604 memcpy(theta, tt, nev*tmpI);
605 tmpI = rank*tmpI;
606 for (i = 0; i < nev; ++i) {
607 memcpy(EV + i*ldEV, kp + i*size, tmpI);
608 }
609
610 break;
611
612 case 1:
613
614 // Use the Cholesky factorization of MM to compute the generalized eigenvectors
615
616 // Define storage
617 kp = new double[size*size];
618 mp = new double[size*size];
619 tt = new double[size];
620
621 // Copy KK & MM
622 tmpI = sizeof(double);
623 for (i = 0; i < size; ++i) {
624 memcpy(kp + i*size, KK + i*ldK, (i+1)*tmpI);
625 memcpy(mp + i*size, MM + i*ldM, (i+1)*tmpI);
626 }
627
628 // Solve the generalized eigenproblem with LAPACK
629 info = 0;
630 callFortran.SYGV(1, 'V', 'U', size, kp, size, mp, size, tt, work, lwork, &info);
631
632 // Treat error messages
633 if (info < 0) {
634 if (verbose > 0) {
635 cerr << endl;
636 cerr << " In DSYGV, argument " << -info << "has an illegal value.\n";
637 cerr << endl;
638 }
639 return -20;
640 }
641 if (info > 0) {
642 if (info > size)
643 nev = 0;
644 else {
645 if (verbose > 0) {
646 cerr << endl;
647 cerr << " In DSYGV, DPOTRF or DSYEV returned an error code (" << info << ").\n";
648 cerr << endl;
649 }
650 return -20;
651 }
652 }
653
654 // Copy the eigenvectors
655 memcpy(theta, tt, nev*tmpI);
656 tmpI = size*tmpI;
657 for (i = 0; i < nev; ++i) {
658 memcpy(EV + i*ldEV, kp + i*size, tmpI);
659 }
660
661 break;
662
663 case 10:
664
665 // Simple eigenproblem
666
667 // Define storage
668 kp = new double[size*size];
669 tt = new double[size];
670
671 // Copy KK
672 tmpI = sizeof(double);
673 for (i=0; i<size; ++i) {
674 memcpy(kp + i*size, KK + i*ldK, (i+1)*tmpI);
675 }
676
677 // Solve the generalized eigenproblem with LAPACK
678 callFortran.SYEV('V', 'U', size, kp, size, tt, work, lwork, &info);
679
680 // Treat error messages
681 if (info != 0) {
682 if (verbose > 0) {
683 cerr << endl;
684 if (info < 0)
685 cerr << " In DSYEV, argument " << -info << " has an illegal value\n";
686 else
687 cerr << " In DSYEV, the algorithm failed to converge (" << info << ").\n";
688 cerr << endl;
689 }
690 info = -20;
691 break;
692 }
693
694 // Copy the eigenvectors
695 memcpy(theta, tt, nev*tmpI);
696 tmpI = size*tmpI;
697 for (i = 0; i < nev; ++i) {
698 memcpy(EV + i*ldEV, kp + i*size, tmpI);
699 }
700
701 break;
702
703 }
704
705 // Clear the memory
706
707 if (kp)
708 delete[] kp;
709 if (mp)
710 delete[] mp;
711 if (tt)
712 delete[] tt;
713 if (work)
714 delete[] work;
715 if (U)
716 delete[] U;
717
718 return info;
719
720}
721
722