001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021package org.biojava.nbio.structure.xtal; 022 023import javax.vecmath.Matrix3d; 024import javax.vecmath.Matrix4d; 025import javax.vecmath.Point3i; 026import javax.vecmath.Vector3d; 027import java.io.Serializable; 028import java.util.Locale; 029 030import static java.lang.Math.abs; 031 032 033/** 034 * Representation of a transformation in a crystal: 035 * - a transformation id (each of the transformations in a space group, 0 to m) 036 * - a crystal translation 037 * The transformation matrix in crystal basis is stored, representing the basic 038 * transformation together with the crystal translation. 039 * Contains methods to check for equivalent transformations. 040 * 041 * 042 * @author duarte_j 043 * 044 */ 045public class CrystalTransform implements Serializable { 046 047 private static final long serialVersionUID = 1L; 048 049 050 public static final Matrix4d IDENTITY = new Matrix4d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1); 051 052 /** 053 * The space group to which this transform belongs 054 */ 055 private final SpaceGroup sg; 056 057 /** 058 * The transform id corresponding to the SpaceGroup's transform indices. 059 * From 0 (identity) to m (m=number of symmetry operations of the space group) 060 * It is unique within the unit cell but equivalent units of different crystal unit cells 061 * will have same id 062 */ 063 private int transformId; 064 065 /** 066 * The 4-dimensional matrix transformation in crystal basis. 067 * Note that the translational component of this matrix is not necessarily 068 * identical to crystalTranslation since some operators have fractional 069 * translations within the cell 070 */ 071 private Matrix4d matTransform; 072 073 /** 074 * The crystal translation (always integer) 075 */ 076 private Point3i crystalTranslation; 077 078 079 /** 080 * Creates a new CrystalTransform representing the identity transform 081 * in cell (0,0,0) 082 */ 083 public CrystalTransform(SpaceGroup sg) { 084 this.sg = sg; 085 this.transformId = 0; 086 this.matTransform = (Matrix4d)IDENTITY.clone(); 087 this.crystalTranslation = new Point3i(0,0,0); 088 } 089 090 /** 091 * Represents the n-th transform 092 * @param sg 093 * @param transformId 094 */ 095 public CrystalTransform(SpaceGroup sg, int transformId) { 096 this.sg = sg; 097 this.transformId = transformId; 098 if (sg==null && transformId==0) { 099 this.matTransform = (Matrix4d)IDENTITY.clone(); 100 } else if (sg==null) { 101 throw new IllegalArgumentException("Space Group cannot be null if transformId!=0"); 102 } else { 103 this.matTransform = (Matrix4d)sg.getTransformation(transformId).clone(); 104 } 105 this.crystalTranslation = new Point3i(0,0,0); 106 } 107 108 /** 109 * Copy constructor 110 * @param transform 111 */ 112 public CrystalTransform(CrystalTransform transform) { 113 this.sg = transform.sg; 114 this.transformId = transform.transformId; 115 this.matTransform = new Matrix4d(transform.matTransform); 116 this.crystalTranslation = new Point3i(transform.crystalTranslation); 117 } 118 119 public Matrix4d getMatTransform() { 120 return matTransform; 121 } 122 123 public void setMatTransform(Matrix4d matTransform) { 124 this.matTransform = matTransform; 125 } 126 127 public Point3i getCrystalTranslation() { 128 return crystalTranslation; 129 } 130 131 public void translate(Point3i translation) { 132 matTransform.m03 = matTransform.m03+translation.x; 133 matTransform.m13 = matTransform.m13+translation.y; 134 matTransform.m23 = matTransform.m23+translation.z; 135 136 crystalTranslation.add(translation); 137 138 } 139 140 /** 141 * Returns true if the given CrystalTransform is equivalent to this one. 142 * Two crystal transforms are equivalent if one is the inverse of the other, i.e. 143 * their transformation matrices multiplication is equal to the identity. 144 * @param other 145 * @return 146 */ 147 public boolean isEquivalent(CrystalTransform other) { 148 Matrix4d mul = new Matrix4d(); 149 mul.mul(this.matTransform,other.matTransform); 150 151 if (mul.epsilonEquals(IDENTITY, 0.0001)) { 152 return true; 153 } 154 return false; 155 } 156 157 /** 158 * Tells whether this transformation is a pure crystal lattice translation, 159 * i.e. no rotational component and an integer translation vector. 160 * @return 161 */ 162 public boolean isPureCrystalTranslation() { 163 return (transformId==0 && (crystalTranslation.x!=0 || crystalTranslation.y!=0 || crystalTranslation.z!=0)); 164 } 165 166 /** 167 * Tells whether this transformation is the identity: no rotation and no translation 168 * @return 169 */ 170 public boolean isIdentity() { 171 return (transformId==0 && crystalTranslation.x==0 && crystalTranslation.y==0 && crystalTranslation.z==0); 172 } 173 174 /** 175 * Tells whether this transformation is a pure translation: 176 * either a pure crystal (lattice) translation or a fractional (within 177 * unit cell) translation: space groups Ixxx, Cxxx, Fxxx have operators 178 * with fractional translations within the unit cell. 179 * @return 180 */ 181 public boolean isPureTranslation() { 182 if (isPureCrystalTranslation()) return true; 183 if (isPureMatrixTranslation()) return true; 184 return false; 185 } 186 187 /** 188 * This method will help check if the matrix translation is pure or not. 189 * @return boolean 190 */ 191 private boolean isPureMatrixTranslation(){ 192 return SpaceGroup.deltaComp(matTransform.m00,1,SpaceGroup.DELTA) && 193 SpaceGroup.deltaComp(matTransform.m01,0,SpaceGroup.DELTA) && 194 SpaceGroup.deltaComp(matTransform.m02,0,SpaceGroup.DELTA) && 195 196 SpaceGroup.deltaComp(matTransform.m10,0,SpaceGroup.DELTA) && 197 SpaceGroup.deltaComp(matTransform.m11,1,SpaceGroup.DELTA) && 198 SpaceGroup.deltaComp(matTransform.m12,0,SpaceGroup.DELTA) && 199 200 SpaceGroup.deltaComp(matTransform.m20,0,SpaceGroup.DELTA) && 201 SpaceGroup.deltaComp(matTransform.m21,0,SpaceGroup.DELTA) && 202 SpaceGroup.deltaComp(matTransform.m22,1,SpaceGroup.DELTA) && 203 (Math.abs(matTransform.m03-0.0)>SpaceGroup.DELTA || Math.abs(matTransform.m13-0.0)>SpaceGroup.DELTA || Math.abs(matTransform.m23-0.0)>SpaceGroup.DELTA); 204 } 205 206 /** 207 * Tells whether this transformation contains a fractional translational 208 * component (whatever its rotational component). A fractional translation 209 * together with a rotation means a screw axis. 210 * @return 211 */ 212 public boolean isFractionalTranslation() { 213 if ((Math.abs(matTransform.m03-crystalTranslation.x)>SpaceGroup.DELTA) || 214 (Math.abs(matTransform.m13-crystalTranslation.y)>SpaceGroup.DELTA) || 215 (Math.abs(matTransform.m23-crystalTranslation.z)>SpaceGroup.DELTA)) { 216 return true; 217 } 218 return false; 219 } 220 221 /** 222 * Tells whether this transformation is a rotation disregarding the translational component, 223 * i.e. either pure rotation or screw rotation, but not improper rotation. 224 * @return 225 */ 226 public boolean isRotation() { 227 // if no SG, that means a non-crystallographic entry (e.g. NMR). We return false 228 if (sg==null) return false; 229 230 // this also takes care of case <0 (improper rotations): won't be considered as rotations 231 if (sg.getAxisFoldType(this.transformId)>1) return true; 232 233 return false; 234 } 235 236 /** 237 * Returns the TransformType of this transformation: AU, crystal translation, fractional translation 238 * , 2 3 4 6-fold rotations, 2 3 4 6-fold screw rotations, -1 -3 -2 -4 -6 inversions/rotoinversions. 239 * @return 240 */ 241 public TransformType getTransformType() { 242 243 // if no SG, that means a non-crystallographic entry (e.g. NMR). We return AU as type 244 if (sg==null) return TransformType.AU; 245 246 int foldType = sg.getAxisFoldType(this.transformId); 247 boolean isScrewOrGlide = false; 248 Vector3d translScrewComponent = getTranslScrewComponent(); 249 if (Math.abs(translScrewComponent.x-0.0)>SpaceGroup.DELTA || 250 Math.abs(translScrewComponent.y-0.0)>SpaceGroup.DELTA || 251 Math.abs(translScrewComponent.z-0.0)>SpaceGroup.DELTA) { 252 253 isScrewOrGlide = true; 254 } 255 256 if (foldType>1) { 257 258 if (isScrewOrGlide) { 259 switch (foldType) { 260 case 2: 261 return TransformType.TWOFOLDSCREW; 262 case 3: 263 return TransformType.THREEFOLDSCREW; 264 case 4: 265 return TransformType.FOURFOLDSCREW; 266 case 6: 267 return TransformType.SIXFOLDSCREW; 268 default: 269 throw new NullPointerException("This transformation did not fall into any of the known types! This is most likely a bug."); 270 } 271 } else { 272 switch (foldType) { 273 case 2: 274 return TransformType.TWOFOLD; 275 case 3: 276 return TransformType.THREEFOLD; 277 case 4: 278 return TransformType.FOURFOLD; 279 case 6: 280 return TransformType.SIXFOLD; 281 default: 282 throw new NullPointerException("This transformation did not fall into any of the known types! This is most likely a bug."); 283 } 284 } 285 286 } else if (foldType<0) { 287 switch (foldType) { 288 case -1: 289 return TransformType.ONEBAR; 290 case -2: 291 if (isScrewOrGlide) { 292 return TransformType.GLIDE; 293 } 294 return TransformType.TWOBAR; 295 case -3: 296 return TransformType.THREEBAR; 297 case -4: 298 return TransformType.FOURBAR; 299 case -6: 300 return TransformType.SIXBAR; 301 default: 302 throw new NullPointerException("This transformation did not fall into any of the known types! This is most likely a bug."); 303 } 304 } else { 305 if (isIdentity()) { 306 return TransformType.AU; 307 } 308 if (isPureCrystalTranslation()) { 309 return TransformType.XTALTRANSL; 310 } 311 if (isFractionalTranslation()) { 312 return TransformType.CELLTRANSL; 313 } 314 throw new NullPointerException("This transformation did not fall into any of the known types! This is most likely a bug."); 315 } 316 317 } 318 319 public Vector3d getTranslScrewComponent() { 320 321 return getTranslScrewComponent(matTransform); 322 323 } 324 325 public int getTransformId() { 326 return transformId; 327 } 328 329 public void setTransformId(int transformId) { 330 this.transformId = transformId; 331 } 332 333 @Override 334 public String toString() { 335 return String.format("[%2d-(%s)]",transformId,toXYZString()); 336 } 337 338 /** 339 * Expresses this transformation in terms of x,y,z fractional coordinates. 340 * 341 * Examples: 342 * @return 343 */ 344 public String toXYZString() { 345 StringBuilder str = new StringBuilder(); 346 347 for(int i=0;i<3;i++) { //for each row 348 boolean emptyRow = true; 349 350 double coef; // TODO work with rational numbers 351 352 353 // X 354 coef = matTransform.getElement(i, 0); 355 356 // Three cases for coef: zero, one, non-one 357 if(abs(coef) > 1e-6 ) { // Non-zero 358 if( abs( abs(coef)-1 ) < 1e-6 ) { // +/- 1 359 if( coef < 0 ) { 360 str.append("-"); 361 } 362 } else { 363 str.append(formatCoef(coef)); 364 str.append("*"); 365 } 366 str.append("x"); 367 emptyRow = false; 368 } 369 370 // Y 371 coef = matTransform.getElement(i, 1); 372 373 if(abs(coef) > 1e-6 ) { // Non-zero 374 if( abs( abs(coef)-1 ) < 1e-6 ) { // +/- 1 375 if( coef < 0 ) { 376 str.append("-"); 377 } else if( !emptyRow ) { 378 str.append("+"); 379 } 380 } else { 381 if( !emptyRow && coef > 0) { 382 str.append("+"); 383 } 384 str.append(formatCoef(coef)); 385 str.append("*"); 386 } 387 str.append("y"); 388 emptyRow = false; 389 } 390 391 // Z 392 coef = matTransform.getElement(i, 2); 393 394 if(abs(coef) > 1e-6 ) { // Non-zero 395 if( abs( abs(coef)-1 ) < 1e-6 ) { // +/- 1 396 if( coef < 0 ) { 397 str.append("-"); 398 } else if( !emptyRow ) { 399 str.append("+"); 400 } 401 } else { 402 if( !emptyRow && coef > 0) { 403 str.append("+"); 404 } 405 str.append(formatCoef(coef)); 406 str.append("*"); 407 } 408 str.append("z"); 409 emptyRow = false; 410 } 411 412 // Intercept 413 coef = matTransform.getElement(i, 3); 414 415 if(abs(coef) > 1e-6 ) { // Non-zero 416 if( !emptyRow && coef > 0) { 417 str.append("+"); 418 } 419 str.append(formatCoef(coef)); 420 } 421 422 if(i<2) { 423 str.append(","); 424 } 425 } 426 427 428 return str.toString(); 429 } 430 /** 431 * helper function to format simple fractions into rationals 432 * @param coef 433 * @return 434 */ 435 private String formatCoef(double coef) { 436 double tol = 1e-6; // rounding tolerance 437 438 // zero case 439 if( Math.abs(coef) < tol) { 440 return "0"; 441 } 442 443 // integer case 444 long num = Math.round(coef); 445 if( Math.abs(num - coef) < tol) { 446 return Long.toString(num); 447 } 448 449 // Other small cases 450 for(int denom = 2; denom < 12; denom++ ) { 451 num = Math.round(coef*denom); 452 if( num - coef*denom < tol ) { 453 return String.format("%d/%d",num, denom); 454 } 455 } 456 457 // Give up and use floating point; 458 return String.format(Locale.US, "%.3f", coef); 459 } 460 461 /** 462 * Given a transformation matrix containing a rotation and translation returns the 463 * screw component of the rotation. 464 * See http://www.crystallography.fr/mathcryst/pdf/Gargnano/Aroyo_Gargnano_1.pdf 465 * @param m 466 * @return 467 */ 468 public static Vector3d getTranslScrewComponent(Matrix4d m) { 469 470 int foldType = SpaceGroup.getRotAxisType(m); 471 // For reference see: 472 // http://www.crystallography.fr/mathcryst/pdf/Gargnano/Aroyo_Gargnano_1.pdf 473 474 Vector3d transl = null; 475 476 Matrix3d W = 477 new Matrix3d(m.m00,m.m01,m.m02, 478 m.m10,m.m11,m.m12, 479 m.m20,m.m21,m.m22); 480 481 if (foldType>=0) { 482 483 // the Y matrix: Y = W^k-1 + W^k-2 ... + W + I ; with k the fold type 484 Matrix3d Y = new Matrix3d(1,0,0, 0,1,0, 0,0,1); 485 Matrix3d Wk = new Matrix3d(1,0,0, 0,1,0, 0,0,1); 486 487 for (int k=0;k<foldType;k++) { 488 Wk.mul(W); // k=0 Wk=W, k=1 Wk=W^2, k=2 Wk=W^3, ... k=foldType-1, Wk=W^foldType 489 if (k!=foldType-1) Y.add(Wk); 490 } 491 492 transl = new Vector3d(m.m03, m.m13, m.m23); 493 Y.transform(transl); 494 495 transl.scale(1.0/foldType); 496 497 } else { 498 499 if (foldType==-2) { // there are glide planes only in -2 500 Matrix3d Y = new Matrix3d(1,0,0, 0,1,0, 0,0,1); 501 Y.add(W); 502 503 transl = new Vector3d(m.m03, m.m13, m.m23); 504 Y.transform(transl); 505 506 transl.scale(1.0/2.0); 507 } else { // for -1, -3, -4 and -6 there's nothing to do: fill with 0s 508 transl = new Vector3d(0,0,0); 509 } 510 } 511 512 return transl; 513 } 514}