1 /* $RCSfile$ 2 * $Author: hansonr $ 3 * $Date: 2008-04-26 01:52:03 -0500 (Sat, 26 Apr 2008) $ 4 * $Revision: 9314 $ 5 * 6 * Copyright (C) 2003-2005 Miguel, Jmol Development, www.jmol.org 7 * 8 * Contact: jmol-developers@lists.sf.net 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Lesser General Public 12 * License as published by the Free Software Foundation; either 13 * version 2.1 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Lesser General Public License for more details. 19 * 20 * You should have received a copy of the GNU Lesser General Public 21 * License along with this library; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 23 */ 24 25 package org.jmol.util; 26 27 import java.util.Arrays; 28 import java.util.Comparator; 29 import java.util.Hashtable; 30 import java.util.Map; 31 32 import javajs.util.Eigen; 33 import javajs.util.M3; 34 import javajs.util.P3; 35 import javajs.util.PT; 36 import javajs.util.Quat; 37 import javajs.util.T3; 38 import javajs.util.V3; 39 40 import javajs.util.BS; 41 42 /** 43 * @author Bob Hanson hansonr@stolaf.edu 6/30/2013 44 * @author Simone Sturniolo 45 */ 46 public class Tensor { 47 48 // factors that give reasonable first views of ellipsoids. 49 50 private static final float ADP_FACTOR = (float) (Math.sqrt(0.5) / Math.PI); 51 private static final float MAGNETIC_SUSCEPTIBILITY_FACTOR = 0.01f; 52 private static final float INTERACTION_FACTOR = 0.04f; 53 private static final float CHEMICAL_SHIFT_ANISOTROPY_FACTOR = 0.01f; 54 55 private static EigenSort tSort; // used for sorting eigenvector/values 56 57 58 // base data: 59 60 public String id; 61 public String type; 62 public int iType = TYPE_OTHER; 63 64 // type is an identifier that the reader/creator delivers: 65 // 66 // adp -- crystallographic displacement parameters 67 // - "erature factors"; t.forThermalEllipsoid = true 68 // - either anisotropic (ADP) or isotropic (IDP) 69 // iso -- isotropic displacement parameters; from org.jmol.symmetry.UnitCell 70 // - changed to "adp" after setting t.isIsotropic = true 71 // ms -- magnetic susceptibility 72 // isc -- NMR interaction tensors 73 // - will have both atomIndex1 and atomIndex2 defined when 74 // - incorporated into a model 75 // charge -- Born Effective Charge tensor 76 // TLS-U -- Translation/Libration/Skew tensor (anisotropic) 77 // TLS-R -- Translation/Libration/Skew tensor (residual) 78 // csa -- Chemical Shift Anisotropy tensor 79 80 private static final String KNOWN_TYPES = 81 ";iso........" + 82 ";adp........" + 83 ";tls-u......" + 84 ";tls-r......" + 85 ";ms........." + 86 ";efg........" + 87 ";isc........" + 88 ";charge....." + 89 ";quadrupole." + 90 ";raman......" + 91 ";csa........"; getType(String type)92 private static int getType(String type) { 93 int pt = type.indexOf("_"); 94 if (pt >= 0) 95 type = type.substring(0, pt); 96 pt = KNOWN_TYPES.indexOf(";" + type.toLowerCase() + "."); 97 return (pt < 0 ? TYPE_OTHER : pt / 11); 98 } 99 100 // these may be augmented, but the order should be kept the same within this list 101 // no types < -1, because these are used in Ellipsoids.getAtomState() as bs.get(iType + 1) 102 103 public static final int TYPE_OTHER = -1; 104 public static final int TYPE_ISO = 0; 105 public static final int TYPE_ADP = 1; 106 public static final int TYPE_TLS_U = 2; 107 public static final int TYPE_TLS_R = 3; 108 public static final int TYPE_MS = 4; 109 public static final int TYPE_EFG = 5; 110 public static final int TYPE_ISC = 6; 111 public static final int TYPE_CHARGE = 7; 112 public static final int TYPE_QUADRUPOLE = 8; 113 public static final int TYPE_RAMAN = 9; 114 public static final int TYPE_CSA = 10; 115 116 public double[][] asymMatrix; 117 public double[][] symMatrix; 118 public V3[] eigenVectors; 119 public float[] eigenValues; 120 public float[] parBorU; // unmodulated 121 122 // derived type-based information, Jmol-centric, for rendering: 123 124 public String altType; // "0" "1" "2" 125 126 // altType is somewhat of a legacy - just lets you use 127 128 // ellipsoid SET 1 129 // ellipsoid SET 2 130 // etc... 131 132 public boolean isIsotropic; // just rendered as balls, not special features 133 public boolean forThermalEllipsoid; 134 public int eigenSignMask = 7; // signs of eigenvalues; bits 2,1,0 set to 1 if > 0 135 private float typeFactor = 1; // an ellipsoid scaling factor depending upon type 136 private boolean sortIso; 137 138 // added only after passing 139 // the tensor to ModelLoader: 140 141 public int modelIndex; 142 public int atomIndex1 = -1; 143 public int atomIndex2 = -1; 144 145 public boolean isModulated; 146 public boolean isUnmodulated; 147 148 private static final String infoList = 149 ";............." + ";eigenvalues.." + ";eigenvectors." 150 + ";asymmatrix..." + ";symmatrix...." + ";value........" 151 + ";isotropy....." + ";anisotropy..." + ";asymmetry...." 152 + ";eulerzyz....." + ";eulerzxz....." + ";quaternion..." 153 + ";indices......" + ";string......." + ";type........." 154 + ";id..........." + ";span........." + ";skew........."; 155 getInfoIndex(String infoType)156 static private int getInfoIndex(String infoType) { 157 if (infoType.charAt(0) != ';') 158 infoType = ";" + infoType + "."; 159 return infoList.indexOf(infoType) / 14; 160 } 161 isFloatInfo(String infoType)162 public static boolean isFloatInfo(String infoType) { 163 switch (getInfoIndex(infoType)) { 164 default: 165 return false; 166 case 5: // value 167 case 6: // isotropy 168 case 7: // anisotropy 169 case 8: // asymmetry 170 case 16: // span 171 case 17: // skew 172 return true; 173 } 174 } 175 176 /** 177 * returns an object of the specified type, including "eigenvalues", 178 * "eigenvectors", "asymmetric", "symmetric", "trace", "indices", and "type" 179 * 180 * @param infoType 181 * @return Object or null 182 */ getInfo(String infoType)183 public Object getInfo(String infoType) { 184 switch (getInfoIndex(infoType)) { 185 default: 186 // dump all key/value pairs 187 Map<String, Object> info = new Hashtable<String, Object>(); 188 String[] s = PT.getTokens(PT.replaceWithCharacter(infoList, ";.", ' ').trim()); 189 Arrays.sort(s); 190 for (int i = 0; i < s.length; i++) { 191 Object o = getInfo(s[i]); 192 if (o != null) 193 info.put(s[i], o); 194 } 195 return info; 196 197 case 1: 198 return eigenValues; 199 case 2: 200 P3[] list = new P3[3]; 201 for (int i = 0; i < 3; i++) 202 list[i] = P3.newP(eigenVectors[i]); 203 return list; 204 205 206 case 3: 207 if (asymMatrix == null) 208 return null; 209 float[] a = new float[9]; 210 int pt = 0; 211 for (int i = 0; i < 3; i++) 212 for (int j = 0; j < 3; j++) 213 a[pt++] = (float) asymMatrix[i][j]; 214 return M3.newA9(a); 215 case 4: 216 if (symMatrix == null) 217 return null; 218 float[] b = new float[9]; 219 int p2 = 0; 220 for (int i = 0; i < 3; i++) 221 for (int j = 0; j < 3; j++) 222 b[p2++] = (float) symMatrix[i][j]; 223 return M3.newA9(b); 224 case 5: // value 225 return Float.valueOf(eigenValues[2]); 226 case 6: // isotropy 227 return Float.valueOf(isotropy()); 228 case 7: // anisotropy 229 // Anisotropy, defined as Vzz-(Vxx+Vyy)/2 230 return Float.valueOf(anisotropy()); 231 case 8: // asymmetry 232 // Asymmetry, defined as (Vyy-Vxx)/(Vzz - Viso) 233 return Float.valueOf(asymmetry()); 234 235 236 case 9: // eulerzyz 237 return ((Quat) getInfo("quaternion")).getEulerZYZ(); 238 case 10: // eulerzxz 239 return ((Quat) getInfo("quaternion")).getEulerZXZ(); 240 case 11: // quaternion 241 return Quat.getQuaternionFrame(null, eigenVectors[0], 242 eigenVectors[1]); 243 244 245 case 12: 246 return new int[] { modelIndex, atomIndex1, atomIndex2 }; 247 case 13: 248 return this.toString(); 249 case 14: 250 return type; 251 252 case 15: 253 return id; 254 255 case 16: 256 return Float.valueOf(span()); 257 case 17: 258 return Float.valueOf(skew()); 259 260 } 261 } 262 263 // isotropy = (e2 + e1 + e0)/3 264 // 265 // | | | 266 // | | | 267 // e2 e1 e0 268 // | 269 // iso 270 271 /** 272 * isotropy = average of eigenvalues 273 * 274 * @return isotropy 275 */ isotropy()276 public float isotropy() { 277 return (eigenValues[0] + eigenValues[1] + eigenValues[2]) / 3; 278 } 279 280 // span = |e2 - e0| 281 // 282 // | | | 283 // | | | 284 // e2 e1 e0 285 // |---------------------------| 286 // span 287 // 288 289 /** 290 * width of the signal; |e2 - e0| 291 * 292 * @return unitless; >= 0 293 */ span()294 public float span() { 295 return Math.abs(eigenValues[2] - eigenValues[0]); 296 } 297 298 // skew = 3 (e1 - iso) / span 299 // 300 // | | | 301 // | iso | | 302 // e2 | e1 e0 303 // e1 - iso |-> 304 // 305 // |---------------------------| 306 // span 307 // 308 // or 0 if 0/0 309 310 /** 311 * a measure of asymmetry. 312 * 313 * @return range [-1, 1] 314 */ skew()315 public float skew() { 316 return (span() == 0 ? 0 : 3 * (eigenValues[1] - isotropy()) / span()); 317 } 318 319 320 // anistropy = e2 - (e1 + e0)/2 321 // 322 // | | | 323 // | | | 324 // e2 e1 e0 325 // <----------------------| 326 // anisotropy 327 328 /** 329 * anisotropy = directed distance from (center of two closest) to (the furthest) 330 * @return unitless number 331 */ anisotropy()332 public float anisotropy() { 333 return eigenValues[2] - (eigenValues[0] + eigenValues[1]) / 2; 334 } 335 336 // reduced anisotropy = e2 - iso = anisotropy * 2/3 337 // 338 // | | | 339 // | | | 340 // e2 iso e1 e0 341 // <----------------------| 342 // anisotropy 343 // <--------------| 344 // reduced anisotropy 345 346 /** 347 * reduced anisotropy = largest difference from isotropy 348 * (may be negative) 349 * 350 * @return unitless number 351 * 352 */ reducedAnisotropy()353 public float reducedAnisotropy() { 354 return anisotropy() * 2 / 3; // = eigenValues[2]-iso(); 355 } 356 357 // asymmetry = (e1 - e0)/(e2 - iso) 358 // 359 // | | | 360 // | | | 361 // e2 iso e1 e0 362 // <--------------| <--------| 363 // (e2 - iso) (e1 - e0) 364 // or 0 when 0/0 365 366 /** 367 * asymmetry = deviation from a symmetric tensor 368 * 369 * @return range [0,1] 370 */ asymmetry()371 public float asymmetry() { 372 return span() == 0 ? 0 : (eigenValues[1] - eigenValues[0]) / reducedAnisotropy(); 373 } 374 copyTensor()375 public Tensor copyTensor() { 376 Tensor t = new Tensor(); 377 t.setType(type); 378 t.eigenValues = eigenValues; 379 t.eigenVectors = eigenVectors; 380 t.asymMatrix = asymMatrix; 381 t.symMatrix = symMatrix; 382 t.eigenSignMask = eigenSignMask; 383 t.modelIndex = modelIndex; 384 t.atomIndex1 = atomIndex1; 385 t.atomIndex2 = atomIndex2; 386 t.parBorU = parBorU; 387 t.id = id; 388 return t; 389 } 390 391 /** 392 * Although this constructor is public, to be a valid tensor, one must invoke one of the 393 * "setFrom" methods. These had been static, but it turns out when that is the case, then 394 * JavaScript versions cannot be modularized to omit this class along with Eigen. So the 395 * general full constructor would look something like: 396 * 397 * new Tensor().setFrom...(....) 398 * 399 * 400 * 401 */ Tensor()402 public Tensor() {} 403 404 /** 405 * Standard constructor for QM tensors 406 * 407 * @param asymmetricTensor 408 * @param type 409 * @param id 410 * @return this 411 */ setFromAsymmetricTensor(double[][] asymmetricTensor, String type, String id)412 public Tensor setFromAsymmetricTensor(double[][] asymmetricTensor, String type, String id) { 413 double[][] a = new double[3][3]; 414 for (int i = 3; --i >= 0;) 415 for (int j = 3; --j >= 0;) 416 a[i][j] = asymmetricTensor[i][j]; 417 418 // symmetrize matrix 419 if (a [0][1] != a[1][0]) { 420 a[0][1] = a[1][0] = (a[0][1] + a[1][0])/2; 421 } 422 if (a[1][2] != a[2][1]) { 423 a[1][2] = a[2][1] = (a[1][2] + a[2][1])/2; 424 } 425 if (a[0][2] != a[2][0]) { 426 a[0][2] = a[2][0] = (a[0][2] + a[2][0])/2; 427 } 428 M3 m = new M3(); 429 float[] mm = new float[9]; 430 for (int i = 0, p = 0; i < 3; i++) 431 for (int j = 0; j < 3; j++) 432 mm[p++] = (float) a[i][j]; 433 m.setA(mm); 434 435 V3[] vectors = new V3[3]; 436 float[] values = new float[3]; 437 new Eigen().setM(a).fillFloatArrays(vectors, values); 438 439 // this code was used for testing only 440 // Eigen e = new Eigen().setM(a); 441 // V3[] evec = eigen.getEigenVectors3(); 442 // V3 n = new V3(); 443 // V3 cross = new V3(); 444 // for (int i = 0; i < 3; i++) { 445 // n.setT(evec[i]); 446 // m.rotate(n); 447 // cross.cross(n, evec[i]); 448 // //Logger.info("v[i], n, n x v[i]"+ evec[i] + " " + n + " " + cross); 449 // n.setT(evec[i]); 450 // n.normalize(); 451 // cross.cross(evec[i], evec[(i + 1) % 3]); 452 // //Logger.info("draw id eigv" + i + " " + Escape.eP(evec[i]) + " color " + (i == 0 ? "red": i == 1 ? "green" : "blue") + " # " + n + " " + cross); 453 // } 454 // Logger.info("eigVal+vec (" + eigen.d[0] + " + " + eigen.e[0] 455 // + ")\n (" + eigen.d[1] + " + " + eigen.e[1] 456 // + ")\n (" + eigen.d[2] + " + " + eigen.e[2] + ")"); 457 458 newTensorType(vectors, values, type, id); 459 asymMatrix = asymmetricTensor; 460 symMatrix = a; 461 this.id = id; 462 return this; 463 } 464 465 /** 466 * Standard constructor for charge and iso. 467 * 468 * @param eigenVectors 469 * @param eigenValues 470 * @param type 471 * @param id 472 * @param t 473 * @return this 474 */ setFromEigenVectors(T3[] eigenVectors, float[] eigenValues, String type, String id, Tensor t)475 public Tensor setFromEigenVectors(T3[] eigenVectors, 476 float[] eigenValues, String type, String id, Tensor t) { 477 float[] values = new float[3]; 478 V3[] vectors = new V3[3]; 479 for (int i = 0; i < 3; i++) { 480 vectors[i] = V3.newV(eigenVectors[i]); 481 values[i] = eigenValues[i]; 482 } 483 newTensorType(vectors, values, type, id); 484 if (t != null) { 485 isModulated = t.isModulated; 486 isUnmodulated = t.isUnmodulated; 487 parBorU = t.parBorU; 488 } 489 return this; 490 } 491 492 /** 493 * Standard constructor for ellipsoids based on axes 494 * 495 * @param axes 496 * @return Tensor 497 */ setFromAxes(V3[] axes)498 public Tensor setFromAxes(V3[] axes) { 499 eigenValues = new float[3]; 500 eigenVectors = new V3[3]; 501 for (int i = 0; i < 3; i++) { 502 eigenVectors[i] = V3.newV(axes[i]); 503 eigenValues[i] = axes[i].length(); 504 if (eigenValues[i] == 0) 505 return null; 506 eigenVectors[i].normalize(); 507 } 508 if (Math.abs(eigenVectors[0].dot(eigenVectors[1])) > 0.0001f 509 || Math.abs(eigenVectors[1].dot(eigenVectors[2])) > 0.0001f 510 || Math.abs(eigenVectors[2].dot(eigenVectors[0])) > 0.0001f) 511 return null; 512 setType("other"); 513 sortAndNormalize(); 514 return this; 515 } 516 517 /** 518 * standard constructor for thermal ellipsoids convention beta 519 * (see http://www.iucr.org/iucr-top/comm/cnom/adp/finrepone/finrepone.html) 520 * 521 * @param coefs 522 * @param id 523 * @return this 524 */ setFromThermalEquation(double[] coefs, String id)525 public Tensor setFromThermalEquation(double[] coefs, String id) { 526 eigenValues = new float[3]; 527 eigenVectors = new V3[3]; 528 this.id = (id == null ? "coefs=" + Escape.eAD(coefs) : id); 529 // assumes an ellipsoid centered on 0,0,0 530 // called by UnitCell for the initial creation from PDB/CIF ADP data 531 double[][] mat = new double[3][3]; 532 mat[0][0] = coefs[0]; //XX 533 mat[1][1] = coefs[1]; //YY 534 mat[2][2] = coefs[2]; //ZZ 535 mat[0][1] = mat[1][0] = coefs[3] / 2; //XY 536 mat[0][2] = mat[2][0] = coefs[4] / 2; //XZ 537 mat[1][2] = mat[2][1] = coefs[5] / 2; //YZ 538 new Eigen().setM(mat).fillFloatArrays(eigenVectors, eigenValues); 539 setType("adp"); 540 sortAndNormalize(); 541 return this; 542 } 543 544 /** 545 * Note that type may be null here to skip type initialization 546 * and allow later setting of type; this should be used with care. 547 * 548 * @param type 549 * @return "this" for convenience only 550 */ setType(String type)551 public Tensor setType(String type) { 552 if (this.type == null || type == null) 553 this.type = type; 554 if (type != null) 555 processType(); 556 return this; 557 } 558 559 /** 560 * Returns a factored eigenvalue; thermal ellipsoids use sqrt(abs(eigenvalue)) for 561 * ellipsoid axes; others use just use abs(eigenvalue); all cases get factored by 562 * typeFactor 563 * 564 * @param i 565 * @return factored eigenvalue 566 */ getFactoredValue(int i)567 public float getFactoredValue(int i) { 568 float f = Math.abs(eigenValues[i]); 569 return (forThermalEllipsoid ? (float) Math.sqrt(f) : f) * typeFactor; 570 } 571 setAtomIndexes(int index1, int index2)572 public void setAtomIndexes(int index1, int index2) { 573 atomIndex1 = index1; 574 atomIndex2 = index2; 575 } 576 isSelected(BS bsSelected, int iAtom)577 public boolean isSelected(BS bsSelected, int iAtom) { 578 return (iAtom >= 0 ? (atomIndex1 == iAtom || atomIndex2 == iAtom) 579 : bsSelected.get(atomIndex1) 580 && (atomIndex2 < 0 || bsSelected.get(atomIndex2))); 581 } 582 583 /** 584 * common processing of eigenvectors. 585 * 586 * @param vectors 587 * @param values 588 * @param type 589 * @param id 590 */ newTensorType(V3[] vectors, float[] values, String type, String id)591 private void newTensorType(V3[] vectors, float[] values, String type, String id) { 592 eigenValues = values; 593 eigenVectors = vectors; 594 for (int i = 0; i < 3; i++) 595 eigenVectors[i].normalize(); 596 setType(type); 597 this.id = id; 598 sortAndNormalize(); 599 eigenSignMask = (eigenValues[0] >= 0 ? 1 : 0) 600 + (eigenValues[1] >= 0 ? 2 : 0) + (eigenValues[2] >= 0 ? 4 : 0); 601 } 602 603 /** 604 * Sets typeFactor, altType, isIsotropic, forThermalEllipsoid; 605 * type "iso" changed to "" here. 606 * 607 */ processType()608 private void processType() { 609 610 forThermalEllipsoid = false; 611 isIsotropic = false; 612 altType = null; 613 typeFactor = 1; 614 sortIso = false; 615 616 switch (iType = getType(type)) { 617 case TYPE_ISO: 618 forThermalEllipsoid = true; 619 isIsotropic = true; 620 altType = "1"; 621 type = "adp"; 622 break; 623 case TYPE_ADP: 624 forThermalEllipsoid = true; 625 typeFactor = ADP_FACTOR; 626 altType = "1"; 627 break; 628 case TYPE_CSA: 629 sortIso = true; 630 typeFactor = CHEMICAL_SHIFT_ANISOTROPY_FACTOR; 631 case TYPE_MS: 632 sortIso = true; 633 typeFactor = MAGNETIC_SUSCEPTIBILITY_FACTOR; 634 break; 635 case TYPE_EFG: 636 sortIso = true; 637 break; 638 case TYPE_ISC: 639 sortIso = true; 640 typeFactor = INTERACTION_FACTOR; 641 break; 642 case TYPE_TLS_R: 643 altType = "2"; 644 break; 645 case TYPE_TLS_U: 646 altType = "3"; 647 break; 648 case TYPE_CHARGE: 649 case TYPE_QUADRUPOLE: 650 break; 651 } 652 } 653 654 /** 655 * The expression: 656 * 657 * |sigma_3 - sigma_iso| >= |sigma_1 - sigma_iso| >= |sigma_2 - sigma_iso| 658 * 659 * simply sorts the values from largest to smallest or smallest to largest, 660 * depending upon the direction of the asymmetry, always setting the last 661 * value to be the farthest from the mean. We use a simpler form here: 662 * 663 * |sigma_3 - sigma_1| >= |sigma_3 - sigma_2| >= |sigma_2 - sigma_1| 664 * 665 * which amounts to the same thing and is prettier. (Think about it!) 666 * 667 */ sortAndNormalize()668 private void sortAndNormalize() { 669 // first sorted 3 2 1, then check for iso-sorting 670 Object[] o = new Object[] { 671 new Object[] { V3.newV(eigenVectors[0]), Float.valueOf(eigenValues[0]) }, 672 new Object[] { V3.newV(eigenVectors[1]), Float.valueOf(eigenValues[1]) }, 673 new Object[] { V3.newV(eigenVectors[2]), Float.valueOf(eigenValues[2]) } }; 674 Arrays.sort(o, getEigenSort()); 675 for (int i = 0; i < 3; i++) { 676 int pt = i; 677 eigenVectors[i] = (V3) ((Object[]) o[pt])[0]; 678 eigenValues[i] = ((Float) ((Object[]) o[pt])[1]).floatValue(); 679 } 680 if (sortIso 681 && eigenValues[2] - eigenValues[1] < eigenValues[1] - eigenValues[0]) { 682 V3 vTemp = eigenVectors[0]; 683 eigenVectors[0] = eigenVectors[2]; 684 eigenVectors[2] = vTemp; 685 float f = eigenValues[0]; 686 eigenValues[0] = eigenValues[2]; 687 eigenValues[2] = f; 688 } 689 for (int i = 0; i < 3; i++) 690 eigenVectors[i].normalize(); 691 } 692 isEquiv(Tensor t)693 public boolean isEquiv(Tensor t) { 694 if (t.iType != iType) 695 return false; 696 float f = Math.abs(eigenValues[0] + eigenValues[1] + eigenValues[2]); 697 for (int i = 0; i < 3; i++) 698 if (Math.abs(t.eigenValues[i] - eigenValues[i]) / f > 0.0003f) 699 return false; 700 return true; 701 } getEigenSort()702 private static Comparator<? super Object> getEigenSort() { 703 return (tSort == null ? (tSort = new EigenSort()) : tSort); 704 } 705 706 @Override toString()707 public String toString() { 708 return (type + " " + modelIndex + " " + atomIndex1 + " " + atomIndex2 + "\n" 709 + (eigenVectors == null ? "" + eigenValues[0] 710 : eigenVectors[0] + "\t" + eigenValues[0] + "\t" + "\n" 711 + eigenVectors[1] + "\t" + eigenValues[1] + "\t" + "\n" 712 + eigenVectors[2] + "\t" + eigenValues[2] + "\t" + "\n")); 713 } 714 715 716 } 717