1 /* $RCSfile$
2  * $Author: egonw $
3  * $Date: 2005-11-10 09:52:44 -0600 (Thu, 10 Nov 2005) $
4  * $Revision: 4255 $
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.symmetry;
26 
27 import java.util.Hashtable;
28 import java.util.Map;
29 
30 import org.jmol.util.Logger;
31 import org.jmol.util.Parser;
32 
33 import javajs.util.M3;
34 import javajs.util.M4;
35 import javajs.util.Matrix;
36 import javajs.util.P3;
37 import javajs.util.PT;
38 import javajs.util.SB;
39 import javajs.util.T3;
40 import javajs.util.V3;
41 
42 /*
43  * Bob Hanson 4/2006
44  *
45  * references: International Tables for Crystallography Vol. A. (2002)
46  *
47  * http://www.iucr.org/iucr-top/cif/cifdic_html/1/cif_core.dic/Ispace_group_symop_operation_xyz.html
48  * http://www.iucr.org/iucr-top/cif/cifdic_html/1/cif_core.dic/Isymmetry_equiv_pos_as_xyz.html
49  *
50  * LATT : http://macxray.chem.upenn.edu/LATT.pdf thank you, Patrick Carroll
51  *
52  * NEVER ACCESS THESE METHODS DIRECTLY! ONLY THROUGH CLASS Symmetry
53  */
54 
55 public class SymmetryOperation extends M4 {
56   String xyzOriginal;
57   String xyzCanonical;
58   String xyz;
59   /**
60    * "normalization" is the process of adjusting symmetry operator definitions
61    * such that the center of geometry of a molecule is within the 555 unit cell
62    * for each operation. It is carried out when "packed" is NOT issued and the
63    * lattice is given as {i j k} or when the lattice is given as {nnn mmm 1}
64    */
65   private boolean doNormalize = true;
66   boolean isFinalized;
67   private int opId;
68   private V3 centering;
69 
70   private static P3 atomTest;
71 
72   private String[] myLabels;
73   int modDim;
74 
75   /**
76    * A linear array for the matrix. Note that the last value in this array may
77    * indicate 120 to indicate that the integer divisor should be 120, not 12.
78    */
79   float[] linearRotTrans;
80 
81   /**
82    * rsvs is the superspace group rotation-translation matrix. It is a (3 +
83    * modDim + 1) x (3 + modDim + 1) matrix from which we can extract all
84    * necessary parts; so 4x4 = 16, 5x5 = 25, 6x6 = 36, 7x7 = 49
85    *
86    * <code>
87      [ [(3+modDim)*x + 1]
88      [(3+modDim)*x + 1]     [ Gamma_R   [0x0]   | Gamma_S
89      [(3+modDim)*x + 1]  ==    [0x0]    Gamma_e | Gamma_d
90      ...                       [0]       [0]    |   1     ]
91      [0 0 0 0 0...   1] ]
92      </code>
93    */
94   Matrix rsvs;
95 
96   boolean isBio;
97   private Matrix sigma;
98   int number;
99   String subsystemCode;
100   int timeReversal;
101 
102   private boolean unCentered;
103   boolean isCenteringOp;
104   private float magOp = Float.MAX_VALUE;
105   int divisor = 12; // could be 120 for magnetic;
106 
setSigma(String subsystemCode, Matrix sigma)107   void setSigma(String subsystemCode, Matrix sigma) {
108     this.subsystemCode = subsystemCode;
109     this.sigma = sigma;
110   }
111 
112   /**
113    *
114    * @param op
115    * @param atoms
116    * @param atomIndex
117    * @param countOrId
118    * @param doNormalize
119    */
SymmetryOperation(SymmetryOperation op, P3[] atoms, int atomIndex, int countOrId, boolean doNormalize)120   SymmetryOperation(SymmetryOperation op, P3[] atoms, int atomIndex,
121       int countOrId, boolean doNormalize) {
122     this.doNormalize = doNormalize;
123     if (op == null) {
124       opId = countOrId;
125       return;
126     }
127     /*
128      * externalizes and transforms an operation for use in atom reader
129      *
130      */
131     xyzOriginal = op.xyzOriginal;
132     xyz = op.xyz;
133     divisor = op.divisor;
134     opId = op.opId;
135     modDim = op.modDim;
136     myLabels = op.myLabels;
137     number = op.number;
138     linearRotTrans = op.linearRotTrans;
139     sigma = op.sigma;
140     subsystemCode = op.subsystemCode;
141     timeReversal = op.timeReversal;
142     setMatrix(false);
143     if (!op.isFinalized)
144       doFinalize();
145     if (doNormalize && sigma == null)
146       setOffset(this, atoms, atomIndex, countOrId);
147   }
148 
setGamma(boolean isReverse)149   private void setGamma(boolean isReverse) {
150     // standard M4 (this)
151     //
152     //  [ [rot]   | [trans]
153     //     [0]    |   1     ]
154     //
155     // becomes for a superspace group
156     //
157     //  rows\cols    (3)    (modDim)    (1)
158     // (3)        [ Gamma_R   [0x0]   | Gamma_S
159     // (modDim)       m*      Gamma_e | Gamma_d
160     // (1)           [0]       [0]    |   1     ]
161 
162     int n = 3 + modDim;
163     double[][] a = (rsvs = new Matrix(null, n + 1, n + 1)).getArray();
164     double[] t = new double[n];
165     int pt = 0;
166     // first retrieve all n x n values from linearRotTrans
167     // and get the translation as well
168     for (int i = 0; i < n; i++) {
169       for (int j = 0; j < n; j++)
170         a[i][j] = linearRotTrans[pt++];
171       t[i] = (isReverse ? -1 : 1) * linearRotTrans[pt++];
172     }
173     a[n][n] = 1;
174     if (isReverse)
175       rsvs = rsvs.inverse();
176     // t is already reversed; set it now.
177     for (int i = 0; i < n; i++)
178       a[i][n] = t[i];
179     // then set this operation matrix as {R|t}
180     a = rsvs.getSubmatrix(0, 0, 3, 3).getArray();
181     for (int i = 0; i < 3; i++)
182       for (int j = 0; j < 4; j++)
183         setElement(i, j, (float) (j < 3 ? a[i][j] : t[i]));
184     setElement(3, 3, 1);
185   }
186 
doFinalize()187   void doFinalize() {
188     div12(this, divisor);
189     if (modDim > 0) {
190       double[][] a = rsvs.getArray();
191       for (int i = a.length - 1; --i >= 0;)
192         a[i][3 + modDim] = finalizeD(a[i][3 + modDim],  divisor);
193     }
194     isFinalized = true;
195   }
196 
div12(M4 op, int divisor)197   private static M4 div12(M4 op, int divisor) {
198     op.m03 = finalizeF(op.m03, divisor);
199     op.m13 = finalizeF(op.m13, divisor);
200     op.m23 = finalizeF(op.m23, divisor);
201     return op;
202   }
203 
finalizeF(float m, int divisor)204   private static float finalizeF(float m, int divisor) {
205     if (divisor == 0) {
206       if (m == 0)
207         return 0;
208       int n = (int) m;
209       return ((n >> DIVISOR_OFFSET) * 1F / (n & DIVISOR_MASK));
210     }
211     return m / divisor;
212   }
213 
finalizeD(double m, int divisor)214   private static double finalizeD(double m, int divisor) {
215     if (divisor == 0) {
216       if (m == 0)
217         return 0;
218       int n = (int) m;
219       return ((n >> DIVISOR_OFFSET) * 1F / (n & DIVISOR_MASK));
220     }
221     return m / divisor;
222   }
223 
getXyz(boolean normalized)224   String getXyz(boolean normalized) {
225     return (normalized && modDim == 0 || xyzOriginal == null ? xyz
226         : xyzOriginal);
227   }
228 
getxyzTrans(P3 t)229   public String getxyzTrans(P3 t) {
230     M4 m = newM4(this);
231     m.add(t);
232     return getXYZFromMatrix(m, false, false, false);
233   }
234 
newPoint(M4 m, P3 atom1, P3 atom2, int x, int y, int z)235   static void newPoint(M4 m, P3 atom1, P3 atom2, int x, int y, int z) {
236     m.rotTrans2(atom1, atom2);
237     atom2.add3(x, y, z);
238   }
239 
dumpInfo()240   String dumpInfo() {
241     return "\n" + xyz + "\ninternal matrix representation:\n" + toString();
242   }
243 
dumpSeitz(M4 s, boolean isCanonical)244   final static String dumpSeitz(M4 s, boolean isCanonical) {
245     SB sb = new SB();
246     float[] r = new float[4];
247     for (int i = 0; i < 3; i++) {
248       s.getRow(i, r);
249       sb.append("[\t");
250       for (int j = 0; j < 3; j++)
251         sb.appendI((int) r[j]).append("\t");
252       float trans = r[3];
253       if (trans != (int) trans)
254         trans = 12 * trans;
255       sb.append(twelfthsOf(isCanonical ? normalizeTwelfths(trans / 12, 12, true) : (int) trans)).append("\t]\n");
256     }
257     return sb.toString();
258   }
259 
setMatrixFromXYZ(String xyz, int modDim, boolean allowScaling)260   boolean setMatrixFromXYZ(String xyz, int modDim, boolean allowScaling) {
261     /*
262      * sets symmetry based on an operator string "x,-y,z+1/2", for example
263      *
264      */
265     if (xyz == null)
266       return false;
267     xyzOriginal = xyz;
268     divisor = setDivisor(xyz);
269     xyz = xyz.toLowerCase();
270     setModDim(modDim);
271     boolean isReverse = (xyz.startsWith("!"));
272     if (isReverse)
273       xyz = xyz.substring(1);
274     if (xyz.indexOf("xyz matrix:") == 0) {
275       /* note: these terms must in unit cell fractional coordinates!
276        * CASTEP CML matrix is in fractional coordinates, but do not take into account
277        * hexagonal systems. Thus, in wurtzite.cml, for P 6c 2'c:
278        *
279        * "transform3":
280        *
281        * -5.000000000000e-1  8.660254037844e-1  0.000000000000e0   0.000000000000e0
282        * -8.660254037844e-1 -5.000000000000e-1  0.000000000000e0   0.000000000000e0
283        *  0.000000000000e0   0.000000000000e0   1.000000000000e0   0.000000000000e0
284        *  0.000000000000e0   0.000000000000e0   0.000000000000e0   1.000000000000e0
285        *
286        * These are transformations of the STANDARD xyz axes, not the unit cell.
287        * But, then, what coordinate would you feed this? Fractional coordinates of what?
288        * The real transform is something like x-y,x,z here.
289        *
290        */
291       this.xyz = xyz;
292       Parser.parseStringInfestedFloatArray(xyz, null, linearRotTrans);
293       return setFromMatrix(null, isReverse);
294     }
295     if (xyz.indexOf("[[") == 0) {
296       xyz = xyz.replace('[', ' ').replace(']', ' ').replace(',', ' ');
297       Parser.parseStringInfestedFloatArray(xyz, null, linearRotTrans);
298       for (int i = linearRotTrans.length; --i >= 0;)
299         if (Float.isNaN(linearRotTrans[i]))
300           return false;
301       setMatrix(isReverse);
302       isFinalized = true;
303       isBio = (xyz.indexOf("bio") >= 0);
304       this.xyz = (isBio ? toString()
305           : getXYZFromMatrix(this, false, false, false));
306       return true;
307     }
308     if (modDim == 0 && xyz.indexOf("x4") >= 0) {
309       for (int i = 14; --i >= 4;) {
310         if (xyz.indexOf("x" + i) >= 0) {
311           setModDim(i - 3);
312           break;
313         }
314       }
315     }
316     String mxyz = null;
317     // we use ",m" and ",-m" as internal notation for time reversal.
318     if (xyz.endsWith("m")) {
319       timeReversal = (xyz.indexOf("-m") >= 0 ? -1 : 1);
320       allowScaling = true;
321     } else if (xyz.indexOf("mz)") >= 0) {
322       // alternatively, we accept notation indicating explicit spin transformation "(mx,my,mz)"
323       int pt = xyz.indexOf("(");
324       mxyz = xyz.substring(pt + 1, xyz.length() - 1);
325       xyz = xyz.substring(0, pt);
326       allowScaling = false;
327     }
328     String strOut = getMatrixFromString(this, xyz, linearRotTrans,
329         allowScaling);
330     if (strOut == null)
331       return false;
332     xyzCanonical = strOut;
333     if (mxyz != null) {
334       // base time reversal on relationship between x and mx in relation to determinant
335       boolean isProper = (M4.newA16(linearRotTrans).determinant3() == 1);
336       timeReversal = (((xyz.indexOf("-x") < 0) == (mxyz
337           .indexOf("-mx") < 0)) == isProper ? 1 : -1);
338     }
339     setMatrix(isReverse);
340     this.xyz = (isReverse ? getXYZFromMatrix(this, true, false, false)
341         : doNormalize ? strOut : xyz);
342     if (timeReversal != 0)
343       this.xyz += (timeReversal == 1 ? ",m" : ",-m");
344     if (Logger.debugging)
345       Logger.debug("" + this);
346     return true;
347   }
348 
349   /**
350    * Sets the divisor to 0 for n/9 or n/mm
351    * @param xyz
352    * @return 0 or 12
353    */
setDivisor(String xyz)354   private static int setDivisor(String xyz) {
355     int pt = xyz.indexOf('/');
356     int len = xyz.length();
357     while (pt > 0 && pt < len - 1) {
358       char c = xyz.charAt(pt + 1);
359       if ("2346".indexOf(c) < 0 || pt < len - 2 && Character.isDigit(xyz.charAt(pt + 2))) {
360         // any n/m where m is not 2,3,4,6
361         // any n/nn
362         return 0;
363       }
364       pt = xyz.indexOf('/', pt + 1);
365     }
366     return 12;
367   }
368 
setModDim(int dim)369   private void setModDim(int dim) {
370     int n = (dim + 4) * (dim + 4);
371     modDim = dim;
372     if (dim > 0)
373       myLabels = labelsXn;
374     linearRotTrans = new float[n];
375   }
376 
setMatrix(boolean isReverse)377   private void setMatrix(boolean isReverse) {
378     if (linearRotTrans.length > 16) {
379       setGamma(isReverse);
380     } else {
381       setA(linearRotTrans);
382       if (isReverse) {
383         P3 p3 = P3.new3(m03, m13, m23);
384         invert();
385         rotate(p3);
386         p3.scale(-1);
387         setTranslation(p3);
388       }
389     }
390   }
391 
setFromMatrix(float[] offset, boolean isReverse)392   boolean setFromMatrix(float[] offset, boolean isReverse) {
393     float v = 0;
394     int pt = 0;
395     myLabels = (modDim == 0 ? labelsXYZ : labelsXn);
396     int rowPt = 0;
397     int n = 3 + modDim;
398     for (int i = 0; rowPt < n; i++) {
399       if (Float.isNaN(linearRotTrans[i]))
400         return false;
401       v = linearRotTrans[i];
402 
403       if (Math.abs(v) < 0.00001f)
404         v = 0;
405       boolean isTrans = ((i + 1) % (n + 1) == 0);
406       if (isTrans) {
407         int denom =  (divisor == 0 ? ((int) v) & DIVISOR_MASK : divisor);
408         if (denom == 0)
409           denom = 12;
410         v = finalizeF(v, divisor);
411         // offset == null only in the case of "xyz matrix:" option
412         if (offset != null) {
413           // magnetic centering only
414           if (pt < offset.length)
415             v += offset[pt++];
416         }
417         v = normalizeTwelfths((v < 0 ? -1 : 1) * Math.abs(v * denom) / denom,
418             denom, doNormalize);
419         if (divisor == 0)
420           v = toDivisor(v, denom);
421         rowPt++;
422       }
423       linearRotTrans[i] = v;
424     }
425     linearRotTrans[linearRotTrans.length - 1] = this.divisor;
426     setMatrix(isReverse);
427     isFinalized = (offset == null);
428     xyz = getXYZFromMatrix(this, true, false, false);
429     return true;
430   }
431 
getMatrixFromXYZ(String xyz)432   public static M4 getMatrixFromXYZ(String xyz) {
433     float[] linearRotTrans = new float[16];
434     xyz = getMatrixFromString(null, "!" + xyz, linearRotTrans, false);
435     return (xyz == null ? null : div12(M4.newA16(linearRotTrans), setDivisor(xyz)));
436   }
437 
438   /**
439    * Convert the Jones-Faithful notation "x, -z+1/2, y" or "x1, x3-1/2, x2,
440    * x5+1/2, -x6+1/2, x7..." to a linear array
441    *
442    * Also allows a-b,-5a-5b,-c;0,0,0 format
443    *
444    * @param op
445    * @param xyz
446    * @param linearRotTrans
447    * @param allowScaling
448    * @return canonized Jones-Faithful string
449    */
getMatrixFromString(SymmetryOperation op, String xyz, float[] linearRotTrans, boolean allowScaling)450   static String getMatrixFromString(SymmetryOperation op, String xyz,
451                                     float[] linearRotTrans,
452                                     boolean allowScaling) {
453     boolean isDenominator = false;
454     boolean isDecimal = false;
455     boolean isNegative = false;
456     int modDim = (op == null ? 0 : op.modDim);
457     int nRows = 4 + modDim;
458     int divisor = (op == null ? setDivisor(xyz) : op.divisor);
459     boolean doNormalize = (op == null ? !xyz.startsWith("!") : op.doNormalize);
460     int dimOffset = (modDim > 0 ? 3 : 0); // allow a b c to represent x y z
461     linearRotTrans[linearRotTrans.length - 1] = 1;
462     // may be a-b,-5a-5b,-c;0,0,0 form
463     int transPt = xyz.indexOf(';') + 1;
464     if (transPt != 0) {
465       allowScaling = true;
466       if (transPt == xyz.length())
467         xyz += "0,0,0";
468     }
469     int rotPt = -1;
470     String[] myLabels = (op == null || modDim == 0 ? null : op.myLabels);
471     if (myLabels == null)
472       myLabels = labelsXYZ;
473     xyz = xyz.toLowerCase() + ",";
474     xyz = xyz.replace('(', ',');
475     //        load =magndata/1.23
476     //        draw symop "-x,-y,-z(mx,my,mz)"
477     if (modDim > 0)
478       xyz = replaceXn(xyz, modDim + 3);
479     int xpt = 0;
480     int tpt0 = 0;
481     int rowPt = 0;
482     char ch;
483     float iValue = 0;
484     int denom = 0;
485     int numer = 0;
486     float decimalMultiplier = 1f;
487     String strT = "";
488     String strOut = "";
489     int[] ret = new int[1];
490     int len = xyz.length();
491     for (int i = 0; i < len; i++) {
492       switch (ch = xyz.charAt(i)) {
493       case ';':
494         break;
495       case '\'':
496       case ' ':
497       case '{':
498       case '}':
499       case '!':
500         continue;
501       case '-':
502         isNegative = true;
503         continue;
504       case '+':
505         isNegative = false;
506         continue;
507       case '/':
508         denom = 0;
509         isDenominator = true;
510         continue;
511       case 'x':
512       case 'y':
513       case 'z':
514       case 'a':
515       case 'b':
516       case 'c':
517       case 'd':
518       case 'e':
519       case 'f':
520       case 'g':
521       case 'h':
522         tpt0 = rowPt * nRows;
523         int ipt = (ch >= 'x' ? ch - 'x' : ch - 'a' + dimOffset);
524         xpt = tpt0 + ipt;
525         int val = (isNegative ? -1 : 1);
526         if (allowScaling && iValue != 0) {
527           linearRotTrans[xpt] = iValue;
528           val = (int) iValue;
529           iValue = 0;
530         } else {
531           linearRotTrans[xpt] = val;
532         }
533         strT += plusMinus(strT, val, myLabels[ipt]);
534         break;
535       case ',':
536         if (transPt != 0) {
537           if (transPt > 0) {
538             // now read translation
539             rotPt = i;
540             i = transPt - 1;
541             transPt = -i;
542             iValue = 0;
543             denom = 0;
544             continue;
545           }
546           transPt = i + 1;
547           i = rotPt;
548         }
549         // add translation in 12ths
550         iValue = normalizeTwelfths(iValue, denom == 0 ? 12 : divisor == 0 ? denom : divisor, doNormalize);
551         linearRotTrans[tpt0 + nRows - 1] = (divisor == 0 && denom > 0 ? iValue = toDivisor(numer, denom) : iValue);
552         strT += xyzFraction12(iValue, (divisor == 0 ? denom : divisor), false, true);
553         // strT += xyzFraction48(iValue, false, true);
554         strOut += (strOut == "" ? "" : ",") + strT;
555         if (rowPt == nRows - 2)
556           return strOut;
557         iValue = 0;
558         numer = 0;
559         denom = 0;
560         strT = "";
561         if (rowPt++ > 2 && modDim == 0) {
562           Logger.warn("Symmetry Operation? " + xyz);
563           return null;
564         }
565         break;
566       case '.':
567         isDecimal = true;
568         decimalMultiplier = 1f;
569         continue;
570       case '0':
571         if (!isDecimal && divisor == 12 && (isDenominator || !allowScaling))
572           continue;
573         //$FALL-THROUGH$
574       default:
575         //Logger.debug(isDecimal + " " + ch + " " + iValue);
576         int ich = ch - '0';
577         if (ich >= 0 && ich <= 9) {
578           if (isDecimal) {
579             decimalMultiplier /= 10f;
580             if (iValue < 0)
581               isNegative = true;
582             iValue += decimalMultiplier * ich * (isNegative ? -1 : 1);
583             continue;
584           }
585           if (isDenominator) {
586             ret[0] = i;
587             denom = PT.parseIntNext(xyz, ret);
588             if (denom < 0)
589               return null;
590             i = ret[0] - 1;
591             if (iValue == 0) {
592               // a/2,....
593               linearRotTrans[xpt] /= denom;
594             } else {
595               numer = (int) iValue;
596               iValue /= denom;
597             }
598           } else {
599             iValue = iValue * 10 + (isNegative ? -1 : 1) * ich;
600             isNegative = false;
601           }
602         } else {
603           Logger.warn("symmetry character?" + ch);
604         }
605       }
606       isDecimal = isDenominator = isNegative = false;
607     }
608     return null;
609   }
610 
replaceXn(String xyz, int n)611   static String replaceXn(String xyz, int n) {
612     for (int i = n; --i >= 0;)
613       xyz = PT.rep(xyz, labelsXn[i], labelsXnSub[i]);
614     return xyz;
615   }
616 
617   private final static int DIVISOR_MASK = 0xFF;
618   private final static int DIVISOR_OFFSET = 8;
619 
toDivisor(float numer, int denom)620   private final static int toDivisor(float numer, int denom) {
621     int n = (int) numer;
622     if (n != numer) {
623       // could happen with magnetic lattice centering 1/5 + 1/2 = 7/10
624       float f = numer - n;
625       denom = (int) Math.abs(denom/f);
626       n = (int) (Math.abs(numer) / f);
627     }
628     return ((n << DIVISOR_OFFSET) + denom);
629   }
630 
xyzFraction12(float n12ths, int denom, boolean allPositive, boolean halfOrLess)631   private final static String xyzFraction12(float n12ths, int denom, boolean allPositive,
632                                             boolean halfOrLess) {
633     float n = n12ths;
634     if (denom != 12) {
635       int in = (int) n;
636       denom = (in & DIVISOR_MASK);
637       n = in >> DIVISOR_OFFSET;
638     }
639     int half = (denom / 2);
640     if (allPositive) {
641       while (n < 0)
642         n += denom;
643     } else if (halfOrLess) {
644       while (n > half)
645         n -= denom;
646       while (n < -half)
647         n += denom;
648     }
649     String s = (denom == 12 ? twelfthsOf(n) : n == 0 ? "0" : n + "/" + denom);
650     return (s.charAt(0) == '0' ? "" : n > 0 ? "+" + s : s);
651   }
652 
653   //  private final static String xyzFraction48ths(float n48ths, boolean allPositive, boolean halfOrLess) {
654   //    float n = n48ths;
655   //    if (allPositive) {
656   //      while (n < 0)
657   //        n += 48f;
658   //    } else if (halfOrLess) {
659   //      while (n > 24f)
660   //        n -= 48f;
661   //      while (n < -24f)
662   //        n += 48f;
663   //    }
664   //    String s = fortyEighthsOf(n);
665   //    return (s.charAt(0) == '0' ? "" : n > 0 ? "+" + s : s);
666   //  }
667 
twelfthsOf(float n12ths)668   final static String twelfthsOf(float n12ths) {
669     String str = "";
670     if (n12ths < 0) {
671       n12ths = -n12ths;
672       str = "-";
673     }
674     int m = 12;
675     int n = Math.round(n12ths);
676     if (Math.abs(n - n12ths) > 0.01f) {
677       // fifths? sevenths? eigths? ninths? sixteenths?
678       // Juan Manuel suggests 10 is large enough here
679       float f = n12ths / 12;
680       int max = 20;
681       for (m = 3; m < max; m++) {
682         float fm = f * m;
683         n = Math.round(fm);
684         if (Math.abs(n - fm) < 0.01f)
685           break;
686       }
687       if (m == max)
688         return str + f;
689     } else {
690       if (n == 12)
691         return str + "1";
692       if (n < 12)
693         return str + twelfths[n % 12];
694       switch (n % 12) {
695       case 0:
696         return "" + n / 12;
697       case 2:
698       case 10:
699         m = 6;
700         break;
701       case 3:
702       case 9:
703         m = 4;
704         break;
705       case 4:
706       case 8:
707         m = 3;
708         break;
709       case 6:
710         m = 2;
711         break;
712       default:
713         break;
714       }
715       n = (n * m / 12);
716     }
717     return str + n + "/" + m;
718   }
719 
720   //  final static String fortyEighthsOf(float n48ths) {
721   //    String str = "";
722   //    if (n48ths < 0) {
723   //      n48ths = -n48ths;
724   //      str = "-";
725   //    }
726   //    int m = 12;
727   //    int n = Math.round(n48ths);
728   //    if (Math.abs(n - n48ths) > 0.01f) {
729   //      // fifths? sevenths? eigths? ninths? sixteenths?
730   //      // Juan Manuel suggests 10 is large enough here
731   //      float f = n48ths / 48;
732   //      int max = 20;
733   //      for (m = 5; m < max; m++) {
734   //        float fm = f * m;
735   //        n = Math.round(fm);
736   //        if (Math.abs(n - fm) < 0.01f)
737   //          break;
738   //      }
739   //      if (m == max)
740   //        return str + f;
741   //    } else {
742   //      if (n == 48)
743   //        return str + "1";
744   //      if (n < 48)
745   //        return str + twelfths[n % 48];
746   //      switch (n % 48) {
747   //      case 0:
748   //        return "" + n / 48;
749   //      case 2:
750   //      case 10:
751   //        m = 6;
752   //        break;
753   //      case 3:
754   //      case 9:
755   //        m = 4;
756   //        break;
757   //      case 4:
758   //      case 8:
759   //        m = 3;
760   //        break;
761   //      case 6:
762   //        m = 2;
763   //        break;
764   //      default:
765   //        break;
766   //      }
767   //      n = (n * m / 12);
768   //    }
769   //    return str + n + "/" + m;
770   //  }
771 
772   private final static String[] twelfths = { "0", "1/12", "1/6", "1/4", "1/3",
773       "5/12", "1/2", "7/12", "2/3", "3/4", "5/6", "11/12" };
774 
775   //  private final static String[] fortyeigths = { "0",
776   //    "1/48", "1/24", "1/16", "1/12",
777   //    "5/48", "1/8", "7/48", "1/6",
778   //    "3/16", "5/24", "11/48", "1/4",
779   //    "13/48", "7/24", "5/16", "1/3",
780   //    "17/48", "3/8", "19/48", "5/12",
781   //    "7/16", "11/24", "23/48", "1/2",
782   //    "25/48", "13/24", "9/16", "7/12",
783   //    "29/48", "15/24", "31/48", "2/3",
784   //    "11/12", "17/16", "35/48", "3/4",
785   //    "37/48", "19/24", "13/16", "5/6",
786   //    "41/48", "7/8", "43/48", "11/12",
787   //    "15/16", "23/24", "47/48"
788   //  };
789   //
plusMinus(String strT, float x, String sx)790   private static String plusMinus(String strT, float x, String sx) {
791     return (x == 0 ? ""
792         : (x < 0 ? "-" : strT.length() == 0 ? "" : "+")
793             + (x == 1 || x == -1 ? "" : "" + (int) Math.abs(x)) + sx);
794   }
795 
normalizeTwelfths(float iValue, int divisor, boolean doNormalize)796   private static float normalizeTwelfths(float iValue, int divisor,
797                                          boolean doNormalize) {
798     iValue *= divisor;
799     int half = divisor / 2;
800     if (doNormalize) {
801       while (iValue > half)
802         iValue -= divisor;
803       while (iValue <= -half)
804         iValue += divisor;
805     }
806     return iValue;
807   }
808 
809   //  private static float normalize48ths(float iValue, boolean doNormalize) {
810   //    iValue *= 48f;
811   //    if (doNormalize) {
812   //      while (iValue > 24)
813   //        iValue -= 48;
814   //      while (iValue <= -24)
815   //        iValue += 48;
816   //    }
817   //    return iValue;
818   //  }
819   //
820   final static String[] labelsXYZ = new String[] { "x", "y", "z" };
821   final static String[] labelsXn = new String[] { "x1", "x2", "x3", "x4", "x5",
822       "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13" };
823   final static String[] labelsXnSub = new String[] { "x", "y", "z", "a", "b",
824       "c", "d", "e", "f", "g", "h", "i", "j" };
825 
getXYZFromMatrix(M4 mat, boolean is12ths, boolean allPositive, boolean halfOrLess)826   final public static String getXYZFromMatrix(M4 mat, boolean is12ths,
827                                               boolean allPositive,
828                                               boolean halfOrLess) {
829     String str = "";
830     SymmetryOperation op = (mat instanceof SymmetryOperation
831         ? (SymmetryOperation) mat
832         : null);
833     if (op != null && op.modDim > 0)
834       return getXYZFromRsVs(op.rsvs.getRotation(), op.rsvs.getTranslation(),
835           is12ths);
836     float[] row = new float[4];
837     int denom = (int) mat.getElement(3, 3);
838     if (denom == 1)
839       denom = 12;
840     else
841       mat.setElement(3, 3, 1);
842     for (int i = 0; i < 3; i++) {
843       int lpt = (i < 3 ? 0 : 3);
844       mat.getRow(i, row);
845       String term = "";
846       for (int j = 0; j < 3; j++)
847         if (row[j] != 0)
848           term += plusMinus(term, row[j], labelsXYZ[j + lpt]);
849       term += xyzFraction12((is12ths ? row[3] : row[3] * denom), denom, allPositive,
850           halfOrLess);
851       str += "," + term;
852     }
853     return str.substring(1);
854   }
855 
setOffset(M4 m, P3[] atoms, int atomIndex, int count)856   public static void setOffset(M4 m, P3[] atoms, int atomIndex, int count) {
857     if (count == 0)
858       return;
859     /*
860      * the center of mass of the full set of atoms is moved into the cell with this
861      *
862      */
863     float x = 0;
864     float y = 0;
865     float z = 0;
866     if (atomTest == null)
867       atomTest = new P3();
868     for (int i = atomIndex, i2 = i + count; i < i2; i++) {
869       newPoint(m, atoms[i], atomTest, 0, 0, 0);
870       x += atomTest.x;
871       y += atomTest.y;
872       z += atomTest.z;
873     }
874     x /= count;
875     y /= count;
876     z /= count;
877     while (x < -0.001 || x >= 1.001) {
878       m.m03 += (x < 0 ? 1 : -1);
879       x += (x < 0 ? 1 : -1);
880     }
881     while (y < -0.001 || y >= 1.001) {
882       m.m13 += (y < 0 ? 1 : -1);
883       y += (y < 0 ? 1 : -1);
884     }
885     while (z < -0.001 || z >= 1.001) {
886       m.m23 += (z < 0 ? 1 : -1);
887       z += (z < 0 ? 1 : -1);
888     }
889   }
890 
891   //  // action of this method depends upon setting of unitcell
892   //  private void transformCartesian(UnitCell unitcell, P3 pt) {
893   //    unitcell.toFractional(pt, false);
894   //    transform(pt);
895   //    unitcell.toCartesian(pt, false);
896   //
897   //  }
898 
rotateAxes(V3[] vectors, UnitCell unitcell, P3 ptTemp, M3 mTemp)899   V3[] rotateAxes(V3[] vectors, UnitCell unitcell, P3 ptTemp, M3 mTemp) {
900     V3[] vRot = new V3[3];
901     getRotationScale(mTemp);
902     for (int i = vectors.length; --i >= 0;) {
903       ptTemp.setT(vectors[i]);
904       unitcell.toFractional(ptTemp, true);
905       mTemp.rotate(ptTemp);
906       unitcell.toCartesian(ptTemp, true);
907       vRot[i] = V3.newV(ptTemp);
908     }
909     return vRot;
910   }
911 
fcoord2(T3 p)912   public String fcoord2(T3 p) {
913     if (divisor == 12)
914       return fcoord(p);
915     return fc2(linearRotTrans[3]) + " " + fc2(linearRotTrans[7]) + " " + fc2(linearRotTrans[11]);
916   }
917 
918   /**
919    * Get string version of fraction when divisor == 0
920    *
921    * @param f
922    * @return "1/2" for example
923    */
fc2(float f)924   private String fc2(float f) {
925       int num = (int) f;
926       int denom = (num & DIVISOR_MASK);
927       num = num >> DIVISOR_OFFSET;
928     return (num == 0 ? "0" : num + "/" + denom);
929   }
930 
931   /**
932    * Get string version of fraction
933    *
934    * @param p
935    * @return "1/2" for example
936    */
fcoord(T3 p)937   static String fcoord(T3 p) {
938     // Castep reader only
939     return fc(p.x) + " " + fc(p.y) + " " + fc(p.z);
940   }
941 
fc(float x)942   private static String fc(float x) {
943     // Castep reader only
944     float xabs = Math.abs(x);
945     String m = (x < 0 ? "-" : "");
946     int x24 = (int) approxF(xabs * 24);
947     if (x24 / 24f == (int) (x24 / 24f))
948       return m + (x24 / 24);
949     if (x24 % 8 != 0) {
950       return m + twelfthsOf(x24 >> 1);
951     }
952     return (x24 == 0 ? "0" : x24 == 24 ? m + "1" : m + (x24 / 8) + "/3");
953   }
954 
approxF(float f)955   static float approxF(float f) {
956     return PT.approx(f, 100);
957   }
958 
getXYZFromRsVs(Matrix rs, Matrix vs, boolean is12ths)959   static String getXYZFromRsVs(Matrix rs, Matrix vs, boolean is12ths) {
960     double[][] ra = rs.getArray();
961     double[][] va = vs.getArray();
962     int d = ra.length;
963     String s = "";
964     for (int i = 0; i < d; i++) {
965       s += ",";
966       for (int j = 0; j < d; j++) {
967         double r = ra[i][j];
968         if (r != 0) {
969           s += (r < 0 ? "-" : s.endsWith(",") ? "" : "+")
970               + (Math.abs(r) == 1 ? "" : "" + (int) Math.abs(r)) + "x"
971               + (j + 1);
972         }
973       }
974       s += xyzFraction12((int) (va[i][0] * (is12ths ? 1 : 12)), 12, false, true);
975     }
976     return PT.rep(s.substring(1), ",+", ",");
977   }
978 
979   @Override
toString()980   public String toString() {
981     return (rsvs == null ? super.toString()
982         : super.toString() + " " + rsvs.toString());
983   }
984 
985   /**
986    * Magnetic spin is a pseudo (or "axial") vector. This means that it acts as a
987    * rotation, not a vector. When a rotation about x is passed through the
988    * mirror plane xz, it is reversed; when it is passed through the mirror plane
989    * yz, it is not reversed -- exactly opposite what you would imagine from a
990    * standard "polar" vector.
991    *
992    * For example, a vector perpendicular to a plane of symmetry (det=-1) will be
993    * flipped (m=1), while a vector parallel to that plane will not be flipped
994    * (m=-1)
995    *
996    * In addition, magnetic spin operations have a flag m=1 or m=-1 (m or -m)
997    * that indicates how the vector quantity changes with symmetry. This is
998    * called "time reversal" and stored here as timeReversal.
999    *
1000    * To apply, timeReversal must be multiplied by the 3x3 determinant, which is
1001    * always 1 (standard rotation) or -1 (rotation-inversion). This we store as
1002    * magOp. See https://en.wikipedia.org/wiki/Pseudovector
1003    *
1004    * @return +1, -1, or 0
1005    */
getMagneticOp()1006   float getMagneticOp() {
1007     return (magOp == Float.MAX_VALUE ? magOp = determinant3() * timeReversal
1008         : magOp);
1009   }
1010 
1011   /**
1012    * set the time reversal, and indicate internally in xyz as appended ",m" or
1013    * ",-m"
1014    *
1015    * @param magRev
1016    */
setTimeReversal(int magRev)1017   void setTimeReversal(int magRev) {
1018     timeReversal = magRev;
1019     if (xyz.indexOf("m") >= 0)
1020       xyz = xyz.substring(0, xyz.indexOf("m"));
1021     if (magRev != 0) {
1022       xyz += (magRev == 1 ? ",m" : ",-m");
1023     }
1024   }
1025 
1026   /**
1027    * assumption here is that these are in order of sets, as in ITA
1028    *
1029    * @return centering
1030    */
getCentering()1031   V3 getCentering() {
1032     if (!isFinalized)
1033       doFinalize();
1034     if (centering == null && !unCentered) {
1035       if (modDim == 0 && m00 == 1 && m11 == 1 && m22 == 1 && m01 == 0
1036           && m02 == 0 && m10 == 0 && m12 == 0 && m20 == 0 && m21 == 0
1037           && (m03 != 0 || m13 != 0 || m23 != 0)) {
1038         isCenteringOp = true;
1039         centering = V3.new3(m03, m13, m23);
1040       } else {
1041         unCentered = true;
1042         centering = null;
1043       }
1044     }
1045     return centering;
1046   }
1047 
fixMagneticXYZ(M4 m, String xyz, boolean addMag)1048   String fixMagneticXYZ(M4 m, String xyz, boolean addMag) {
1049     if (timeReversal == 0)
1050       return xyz;
1051     int pt = xyz.indexOf("m");
1052     pt -= (3 - timeReversal) / 2;
1053     xyz = (pt < 0 ? xyz : xyz.substring(0, pt));
1054     if (!addMag)
1055       return xyz + (timeReversal > 0 ? " +1" : " -1");
1056     M4 m2 = M4.newM4(m);
1057     m2.m03 = m2.m13 = m2.m23 = 0;
1058     if (getMagneticOp() < 0)
1059       m2.scale(-1); // does not matter that we flip m33 - it is never checked
1060     xyz += "(" + PT.rep(PT
1061         .rep(PT.rep(SymmetryOperation.getXYZFromMatrix(m2, false, false, false),
1062             "x", "mx"), "y", "my"),
1063         "z", "mz") + ")";
1064     return xyz;
1065   }
1066 
1067   private Hashtable<String, Object> info;
1068 
getInfo()1069   public Map<String, Object> getInfo() {
1070     if (info == null) {
1071       info = new Hashtable<String, Object>();
1072       info.put("xyz", xyz);
1073       if (centering != null)
1074         info.put("centering", centering);
1075       info.put("index", Integer.valueOf(number - 1));
1076       info.put("isCenteringOp", Boolean.valueOf(isCenteringOp));
1077       if (linearRotTrans != null)
1078         info.put("linearRotTrans", linearRotTrans);
1079       info.put("modulationDimension", Integer.valueOf(modDim));
1080       info.put("matrix", M4.newM4(this));
1081       if (magOp != Float.MAX_VALUE)
1082         info.put("magOp", Float.valueOf(magOp));
1083       info.put("id", Integer.valueOf(opId));
1084       info.put("timeReversal", Integer.valueOf(timeReversal));
1085       if (xyzOriginal != null)
1086         info.put("xyzOriginal", xyzOriginal);
1087     }
1088     return info;
1089   }
1090 
1091 }
1092