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