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