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.*; 024import java.io.Serializable; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Locale; 028 029//import org.slf4j.Logger; 030//import org.slf4j.LoggerFactory; 031 032/** 033 * A crystal cell's parameters. 034 * 035 * @author duarte_j 036 * 037 */ 038public class CrystalCell implements Serializable { 039 040 private static final long serialVersionUID = 1L; 041 //private static final Logger logger = LoggerFactory.getLogger(CrystalCell.class); 042 043 public static final double MIN_VALID_CELL_SIZE = 10.0; // the minimum admitted for a crystal cell 044 045 046 private double a; 047 private double b; 048 private double c; 049 050 private double alpha; 051 private double beta; 052 private double gamma; 053 054 private double alphaRad; 055 private double betaRad; 056 private double gammaRad; 057 058 private double volume; // cached volume 059 060 private double maxDimension; // cached max dimension 061 062 private Matrix3d M; // cached basis change transformation matrix 063 private Matrix3d Minv; // cached basis change transformation matrix 064 private Matrix3d Mtransp; 065 private Matrix3d MtranspInv; 066 067 public CrystalCell() { 068 069 } 070 071 public CrystalCell(double a, double b, double c, double alpha, double beta, double gamma){ 072 this.a = a; 073 this.b = b; 074 this.c = c; 075 this.setAlpha(alpha); // initialises both alpha and alphaRad 076 this.setBeta(beta); 077 this.setGamma(gamma); 078 } 079 080 public double getA() { 081 return a; 082 } 083 084 public void setA(double a) { 085 this.a = a; 086 } 087 088 public double getB() { 089 return b; 090 } 091 092 public void setB(double b) { 093 this.b = b; 094 } 095 096 public double getC() { 097 return c; 098 } 099 100 public void setC(double c) { 101 this.c = c; 102 } 103 104 public double getAlpha() { 105 return alpha; 106 } 107 108 public void setAlpha(double alpha) { 109 this.alpha = alpha; 110 this.alphaRad = Math.toRadians(alpha); 111 } 112 113 public double getBeta() { 114 return beta; 115 } 116 117 public void setBeta(double beta) { 118 this.beta = beta; 119 this.betaRad = Math.toRadians(beta); 120 } 121 122 public double getGamma() { 123 return gamma; 124 } 125 126 public void setGamma(double gamma) { 127 this.gamma = gamma; 128 this.gammaRad = Math.toRadians(gamma); 129 } 130 131 /** 132 * Returns the volume of this unit cell. 133 * See http://en.wikipedia.org/wiki/Parallelepiped 134 * @return 135 */ 136 public double getVolume() { 137 if (volume!=0) { 138 return volume; 139 } 140 volume = a*b*c* 141 Math.sqrt(1-Math.cos(alphaRad)*Math.cos(alphaRad)-Math.cos(betaRad)*Math.cos(betaRad)-Math.cos(gammaRad)*Math.cos(gammaRad) 142 +2.0*Math.cos(alphaRad)*Math.cos(betaRad)*Math.cos(gammaRad)); 143 144 return volume; 145 } 146 147 /** 148 * Get the index of a unit cell to which the query point belongs. 149 * 150 * <p>For instance, all points in the unit cell at the origin will return (0,0,0); 151 * Points in the unit cell one unit further along the `a` axis will return (1,0,0), 152 * etc. 153 * @param pt Input point (in orthonormal coordinates) 154 * @return A new point with the three indices of the cell containing pt 155 */ 156 public Point3i getCellIndices(Tuple3d pt) { 157 Point3d p = new Point3d(pt); 158 this.transfToCrystal(p); 159 160 int x = (int)Math.floor(p.x); 161 int y = (int)Math.floor(p.y); 162 int z = (int)Math.floor(p.z); 163 return new Point3i(x,y,z); 164 } 165 166 /** 167 * Converts the coordinates in pt so that they occur within the (0,0,0) unit cell 168 * @param pt 169 */ 170 public void transfToOriginCell(Tuple3d pt) { 171 transfToCrystal(pt); 172 173 // convert all coordinates to [0,1) interval 174 pt.x = pt.x<0 ? (pt.x%1.0 + 1.0)%1.0 : pt.x%1.0; 175 pt.y = pt.y<0 ? (pt.y%1.0 + 1.0)%1.0 : pt.y%1.0; 176 pt.z = pt.z<0 ? (pt.z%1.0 + 1.0)%1.0 : pt.z%1.0; 177 178 transfToOrthonormal(pt); 179 } 180 181 /** 182 * Converts a set of points so that the reference point falls in the unit cell. 183 * 184 * This is useful to transform a whole chain at once, allowing some of the 185 * atoms to be outside the unit cell, but forcing the centroid to be within it. 186 * 187 * @param points A set of points to transform (in orthonormal coordinates) 188 * @param reference The reference point, which is unmodified but which would 189 * be in the unit cell were it to have been transformed. It is safe to 190 * use a member of the points array here. 191 */ 192 public void transfToOriginCell(Tuple3d[] points, Tuple3d reference) { 193 reference = new Point3d(reference);//clone 194 transfToCrystal(reference); 195 196 int x = (int)Math.floor(reference.x); 197 int y = (int)Math.floor(reference.y); 198 int z = (int)Math.floor(reference.z); 199 200 for( Tuple3d point: points ) { 201 transfToCrystal(point); 202 point.x -= x; 203 point.y -= y; 204 point.z -= z; 205 transfToOrthonormal(point); 206 } 207 } 208 209 /** 210 * 211 * @param ops Set of operations in orthonormal coordinates 212 * @param reference Reference point, which should be in the unit cell after 213 * each operation (also in orthonormal coordinates) 214 * @return A set of orthonormal operators with equivalent rotation to the 215 * inputs, but with translation such that the reference point would fall 216 * within the unit cell 217 */ 218 public Matrix4d[] transfToOriginCellOrthonormal(Matrix4d[] ops, Tuple3d reference) { 219 // reference in crystal coords 220 Point3d refXtal = new Point3d(reference); 221 transfToCrystal(refXtal); 222 223 // Convert to crystal coords 224 Matrix4d[] opsXtal = new Matrix4d[ops.length]; 225 for(int i=0;i<ops.length;i++) { 226 opsXtal[i] = transfToCrystal(ops[i]); 227 } 228 229 Matrix4d[] transformed = transfToOriginCellCrystal(opsXtal, refXtal); 230 231 // Convert back to orthonormal 232 for(int i=0;i<ops.length;i++) { 233 transformed[i] = transfToOrthonormal(transformed[i]); 234 } 235 return transformed; 236 } 237 /** 238 * 239 * @param ops Set of operations in crystal coordinates 240 * @param reference Reference point, which should be in the unit cell after 241 * each operation (also in crystal coordinates) 242 * @return A set of crystal operators with equivalent rotation to the 243 * inputs, but with translation such that the reference point would fall 244 * within the unit cell 245 */ 246 public Matrix4d[] transfToOriginCellCrystal(Matrix4d[] ops, Tuple3d reference) { 247 Matrix4d[] transformed = new Matrix4d[ops.length]; 248 for(int j=0;j<ops.length;j++) { 249 Matrix4d op = ops[j]; 250 251 // transform the reference point 252 Point3d xXtal = new Point3d(reference); 253 op.transform(xXtal); 254 255 // Calculate unit cell of transformed reference 256 int x = (int)Math.floor(xXtal.x); 257 int y = (int)Math.floor(xXtal.y); 258 int z = (int)Math.floor(xXtal.z); 259 260 Matrix4d translation = new Matrix4d(); 261 translation.set(new Vector3d(-x,-y,-z)); 262 263 // Compose op with an additional translation operator 264 translation.mul(op); 265 266 Point3d ref2 = new Point3d(reference); 267 translation.transform(ref2); 268 269 transformed[j] = translation; 270 } 271 272 return transformed; 273 } 274 275 /** 276 * Transform given Matrix4d in crystal basis to the orthonormal basis using 277 * the PDB axes convention (NCODE=1) 278 * @param m 279 * @return 280 */ 281 public Matrix4d transfToOrthonormal(Matrix4d m) { 282 Vector3d trans = new Vector3d(m.m03,m.m13,m.m23); 283 transfToOrthonormal(trans); 284 285 Matrix3d rot = new Matrix3d(); 286 m.getRotationScale(rot); 287 // see Giacovazzo section 2.E, eq. 2.E.1 288 // Rprime = MT-1 * R * MT 289 rot.mul(this.getMTranspose()); 290 rot.mul(this.getMTransposeInv(),rot); 291 292 return new Matrix4d(rot,trans,1.0); 293 } 294 295 /** 296 * Transforms the given crystal basis coordinates into orthonormal coordinates. 297 * e.g. transfToOrthonormal(new Point3d(1,1,1)) returns the orthonormal coordinates of the 298 * vertex of the unit cell. 299 * See Giacovazzo section 2.E, eq. 2.E.1 (or any linear algebra manual) 300 * @param v 301 */ 302 public void transfToOrthonormal(Tuple3d v) { 303 // see Giacovazzo section 2.E, eq. 2.E.1 304 getMTransposeInv().transform(v); 305 } 306 307 /** 308 * Transform given Matrix4d in orthonormal basis to the crystal basis using 309 * the PDB axes convention (NCODE=1) 310 * @param m 311 * @return 312 */ 313 public Matrix4d transfToCrystal(Matrix4d m) { 314 Vector3d trans = new Vector3d(m.m03,m.m13,m.m23); 315 transfToCrystal(trans); 316 317 Matrix3d rot = new Matrix3d(); 318 m.getRotationScale(rot); 319 // see Giacovazzo section 2.E, eq. 2.E.1 (the inverse equation) 320 // R = MT * Rprime * MT-1 321 rot.mul(this.getMTransposeInv()); 322 rot.mul(this.getMTranspose(),rot); 323 324 return new Matrix4d(rot,trans,1.0); 325 } 326 327 /** 328 * Transforms the given orthonormal basis coordinates into crystal coordinates. 329 * See Giacovazzo eq 2.20 (or any linear algebra manual) 330 * @param v 331 */ 332 public void transfToCrystal(Tuple3d v) { 333 getMTranspose().transform(v); 334 } 335 336 /** 337 * Returns the change of basis (crystal to orthonormal) transform matrix, that is 338 * M inverse in the notation of Giacovazzo. 339 * Using the PDB axes convention 340 * (CCP4 uses NCODE identifiers to distinguish the different conventions, the PDB one is called NCODE=1) 341 * The matrix is only calculated upon first call of this method, thereafter it is cached. 342 * See "Fundamentals of Crystallography" C. Giacovazzo, section 2.5 (eq 2.30) 343 * 344 * The non-standard orthogonalisation codes (NCODE for ccp4) are flagged in REMARK 285 after 2011's remediation 345 * with text: "THE ENTRY COORDINATES ARE NOT PRESENTED IN THE STANDARD CRYSTAL FRAME". There were only 148 PDB 346 * entries with non-standard code in 2011. See: 347 * http://www.wwpdb.org/documentation/2011remediation_overview-061711.pdf 348 * The SCALE1,2,3 records contain the correct transformation matrix (what Giacovazzo calls M matrix). 349 * In those cases if we calculate the M matrix following Giacovazzo's equations here, we get an entirely wrong one. 350 * Examples of PDB with non-standard orthogonalisation are 1bab and 1bbb. 351 * @return 352 */ 353 private Matrix3d getMInv() { 354 if (Minv!=null) { 355 return Minv; 356 } 357 358 // see eq. 2.30 Giacovazzo 359 Minv = new Matrix3d( this.a, 0, 0, 360 this.b*Math.cos(gammaRad), this.b*Math.sin(gammaRad), 0, 361 this.c*Math.cos(betaRad) , -this.c*Math.sin(betaRad)*getCosAlphaStar(), 1.0/getCstar()); 362 return Minv; 363 } 364 365 // another axes convention (from Giacovazzo) eq. 2.31b, not sure what would be the NCODE of this 366// private Matrix3d getMInv() { 367// if (Minv!=null) { 368// return Minv; 369// } 370// Minv = new Matrix3d( 1/getAstar(), -(getCosGammaStar()/getSinGammaStar())/getAstar(), this.a*Math.cos(betaRad), 371// 0, 1/(getBstar()*getSinGammaStar()), this.b*Math.sin(alphaRad), 372// 0, 0, this.c); 373// return Minv; 374// } 375 376 // and yet another axes convention (from Giacovazzo) eq. 2.31c, not sure what would be the NCODE of this 377// private Matrix3d getMInv() { 378// if (Minv!=null) { 379// return Minv; 380// } 381// Minv = new Matrix3d( alphaRad*Math.sin(gammaRad)*getSinBetaStar(), this.a*Math.cos(gammaRad), this.a*Math.sin(gammaRad)*getCosBetaStar(), 382// 0, this.b, 0, 383// 0, this.c*Math.cos(alphaRad), this.c*Math.sin(alphaRad)); 384// return Minv; 385// } 386 387 // relationships among direct and reciprocal lattice parameters 388 // see Table 2.1 of chapter 2 of Giacovazzo 389 @SuppressWarnings("unused") 390 private double getAstar() { 391 return (this.b*this.c*Math.sin(alphaRad))/getVolume(); 392 } 393 394 @SuppressWarnings("unused") 395 private double getBstar() { 396 return (this.a*this.c*Math.sin(betaRad))/getVolume(); 397 } 398 399 private double getCstar() { 400 return (this.a*this.b*Math.sin(gammaRad))/getVolume(); 401 } 402 403 private double getCosAlphaStar() { 404 return (Math.cos(betaRad)*Math.cos(gammaRad)-Math.cos(alphaRad))/(Math.sin(betaRad)*Math.sin(gammaRad)); 405 } 406 407 @SuppressWarnings("unused") 408 private double getCosBetaStar() { 409 return (Math.cos(alphaRad)*Math.cos(gammaRad)-Math.cos(betaRad))/(Math.sin(alphaRad)*Math.sin(gammaRad)); 410 } 411 412 @SuppressWarnings("unused") 413 private double getCosGammaStar() { 414 return (Math.cos(alphaRad)*Math.cos(betaRad)-Math.cos(gammaRad))/(Math.sin(alphaRad)*Math.sin(betaRad)); 415 } 416 417 @SuppressWarnings("unused") 418 private double getSinAlphaStar() { 419 return getVolume()/(this.a*this.b*this.c*Math.sin(betaRad)*Math.sin(gammaRad)); 420 } 421 422 @SuppressWarnings("unused") 423 private double getSinBetaStar() { 424 return getVolume()/(this.a*this.b*this.c*Math.sin(alphaRad)*Math.sin(gammaRad)); 425 } 426 427 @SuppressWarnings("unused") 428 private double getSinGammaStar() { 429 return getVolume()/(this.a*this.b*this.c*Math.sin(alphaRad)*Math.sin(betaRad)); 430 } 431 432 /** 433 * Returns the change of basis (orthonormal to crystal) transform matrix, that is 434 * M in the notation of Giacovazzo. 435 * Using the PDB convention (NCODE=1). 436 * The matrix is only calculated upon first call of this method, thereafter it is cached. 437 * See "Fundamentals of Crystallography" C. Giacovazzo, section 2.5 438 * @return 439 */ 440 private Matrix3d getM() { 441 if (M!=null){ 442 return M; 443 } 444 M = new Matrix3d(); 445 M.invert(getMInv()); 446 return M; 447 } 448 449 public Matrix3d getMTranspose() { 450 if (Mtransp!=null){ 451 return Mtransp; 452 } 453 Matrix3d M = getM(); 454 Mtransp = new Matrix3d(); 455 Mtransp.transpose(M); 456 return Mtransp; 457 } 458 459 private Matrix3d getMTransposeInv() { 460 if (MtranspInv!=null){ 461 return MtranspInv; 462 } 463 MtranspInv = new Matrix3d(); 464 MtranspInv.invert(getMTranspose()); 465 return MtranspInv; 466 } 467 468 /** 469 * Gets the maximum dimension of the unit cell. 470 * @return 471 */ 472 public double getMaxDimension() { 473 if (maxDimension!=0) { 474 return maxDimension; 475 } 476 Point3d vert0 = new Point3d(0,0,0); 477 Point3d vert1 = new Point3d(1,0,0); 478 transfToOrthonormal(vert1); 479 Point3d vert2 = new Point3d(0,1,0); 480 transfToOrthonormal(vert2); 481 Point3d vert3 = new Point3d(0,0,1); 482 transfToOrthonormal(vert3); 483 Point3d vert4 = new Point3d(1,1,0); 484 transfToOrthonormal(vert4); 485 Point3d vert5 = new Point3d(1,0,1); 486 transfToOrthonormal(vert5); 487 Point3d vert6 = new Point3d(0,1,1); 488 transfToOrthonormal(vert6); 489 Point3d vert7 = new Point3d(1,1,1); 490 transfToOrthonormal(vert7); 491 492 ArrayList<Double> vertDists = new ArrayList<Double>(); 493 vertDists.add(vert0.distance(vert7)); 494 vertDists.add(vert3.distance(vert4)); 495 vertDists.add(vert1.distance(vert6)); 496 vertDists.add(vert2.distance(vert5)); 497 maxDimension = Collections.max(vertDists); 498 return maxDimension; 499 } 500 501 /** 502 * Given a scale matrix parsed from the PDB entry (SCALE1,2,3 records), 503 * checks that the matrix is a consistent scale matrix by comparing the 504 * cell volume to the inverse of the scale matrix determinant (tolerance of 1/100). 505 * If they don't match false is returned. 506 * See the PDB documentation for the SCALE record. 507 * See also last equation of section 2.5 of "Fundamentals of Crystallography" C. Giacovazzo 508 * @param scaleMatrix 509 * @return 510 */ 511 public boolean checkScaleMatrixConsistency(Matrix4d scaleMatrix) { 512 513 double vol = getVolume(); 514 Matrix3d m = new Matrix3d(); 515 scaleMatrix.getRotationScale(m); 516 517 // note we need to have a relaxed tolerance here as the PDB scale matrix is given with not such high precision 518 // plus we don't want to have false positives, so we stay conservative 519 double tolerance = vol/100.0; 520 if ((Math.abs(vol - 1.0/m.determinant() )>tolerance)) { 521 //System.err.println("Warning! SCALE matrix from PDB does not match 1/determinat == cell volume: "+ 522 // String.format("vol=%6.3f 1/det=%6.3f",vol,1.0/m.determinant())); 523 return false; 524 } 525 // this would be to check our own matrix, must always match! 526 //if (!deltaComp(vol,1.0/getMTranspose().determinant())) { 527 // System.err.println("Our calculated SCALE matrix does not match 1/det=cell volume"); 528 //} 529 530 return true; 531 532 } 533 534 /** 535 * Given a scale matrix parsed from a PDB entry (SCALE1,2,3 records), 536 * compares it to our calculated Mtranspose matrix to see if they coincide and 537 * returns true if they do. 538 * If they don't that means that the PDB entry is not in the standard 539 * orthogonalisation (NCODE=1 in ccp4). 540 * In 2011's remediation only 148 PDB entries were found not to be in 541 * a non-standard orthogonalisation. See: 542 * http://www.wwpdb.org/documentation/2011remediation_overview-061711.pdf 543 * For normal cases the scale matrix is diagonal without a translation component. 544 * Additionally the translation component of the SCALE matrix is also checked to 545 * make sure it is (0,0,0), if not false is return 546 * @param scaleMatrix 547 * @return 548 */ 549 public boolean checkScaleMatrix(Matrix4d scaleMatrix) { 550 551 Matrix3d mtranspose = getMTranspose(); 552 for (int i=0;i<3;i++) { 553 for (int j=0;j<3;j++) { 554 if (!deltaComp(mtranspose.getElement(i, j),scaleMatrix.getElement(i, j))) { 555 //System.out.println("Our value ("+i+","+j+"): "+getM().getElement(i,j)); 556 //System.out.println("Their value ("+i+","+j+"): "+scaleMatrix.getElement(i,j)); 557 return false; 558 } 559 } 560 } 561 for (int i=0;i<3;i++) { 562 if (!deltaComp(scaleMatrix.getElement(i, 3),0)) { 563 return false; 564 } 565 } 566 return true; 567 } 568 569 private boolean deltaComp(double d1, double d2) { 570 if (Math.abs(d1-d2)<0.0001) return true; 571 return false; 572 } 573 574 /** 575 * Checks whether the dimensions of this crystal cell are reasonable for protein 576 * crystallography: if all 3 dimensions are below {@value #MIN_VALID_CELL_SIZE} the cell 577 * is considered unrealistic and false returned 578 * @return 579 */ 580 public boolean isCellReasonable() { 581 // this check is necessary mostly when reading PDB files that can contain the default 1 1 1 crystal cell 582 // if we use that further things can go wrong, for instance for interface calculation 583 // For instance programs like coot produce by default a 1 1 1 cell 584 585 if (this.getA()<MIN_VALID_CELL_SIZE && 586 this.getB()<MIN_VALID_CELL_SIZE && 587 this.getC()<MIN_VALID_CELL_SIZE) { 588 return false; 589 } 590 591 return true; 592 593 } 594 595 @Override 596 public String toString() { 597 return String.format(Locale.US, "a%7.2f b%7.2f c%7.2f alpha%6.2f beta%6.2f gamma%6.2f", a, b, c, alpha, beta, gamma); 598 } 599}