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.symmetry.core; 022 023import org.biojava.nbio.structure.symmetry.geometry.MomentsOfInertia; 024import org.biojava.nbio.structure.symmetry.geometry.SuperPosition; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028import javax.vecmath.*; 029import java.util.*; 030 031public class RotationAxisAligner extends AxisAligner{ 032 033 private static final Logger LOGGER = LoggerFactory.getLogger(RotationAxisAligner.class); 034 035 private static final Vector3d X_AXIS = new Vector3d(1,0,0); 036 private static final Vector3d Y_AXIS = new Vector3d(0,1,0); 037 private static final Vector3d Z_AXIS = new Vector3d(0,0,1); 038 039 private Subunits subunits = null; 040 private RotationGroup rotationGroup = null; 041 042 private Matrix4d transformationMatrix = new Matrix4d(); 043 private Matrix4d reverseTransformationMatrix = new Matrix4d(); 044 private Vector3d referenceVector = new Vector3d(); 045 private Vector3d principalRotationVector = new Vector3d(); 046 private Vector3d[] principalAxesOfInertia = null; 047 List<List<Integer>> alignedOrbits = null; 048 049 private Vector3d minBoundary = new Vector3d(); 050 private Vector3d maxBoundary = new Vector3d(); 051 private double xyRadiusMax = Double.MIN_VALUE; 052 053 boolean modified = true; 054 055 public RotationAxisAligner(QuatSymmetryResults results) { 056 this.subunits = results.getSubunits(); 057 this.rotationGroup = results.getRotationGroup(); 058 059 if (subunits == null) { 060 throw new IllegalArgumentException("AxisTransformation: Subunits are null"); 061 } else if (rotationGroup == null) { 062 throw new IllegalArgumentException("AxisTransformation: RotationGroup is null"); 063 } else if (subunits.getSubunitCount() == 0) { 064 throw new IllegalArgumentException("AxisTransformation: Subunits is empty"); 065 } else if (rotationGroup.getOrder() == 0) { 066 throw new IllegalArgumentException("AxisTransformation: RotationGroup is empty"); 067 } 068 } 069 070 /* (non-Javadoc) 071 * @see org.biojava.nbio.structure.quaternary.core.AxisAligner#getTransformation() 072 */ 073 @Override 074 public String getSymmetry() { 075 run(); 076 return rotationGroup.getPointGroup(); 077 } 078 079 @Override 080 public Matrix4d getTransformation() { 081 run(); 082 return transformationMatrix; 083 } 084 085 @Override 086 public Matrix3d getRotationMatrix() { 087 run(); 088 Matrix3d m = new Matrix3d(); 089 transformationMatrix.getRotationScale(m); 090 return m; 091 } 092 093 @Override 094 public Matrix4d getReverseTransformation() { 095 run(); 096 return reverseTransformationMatrix; 097 } 098 099 @Override 100 public Vector3d getPrincipalRotationAxis() { 101 run(); 102 return principalRotationVector; 103 } 104 105 @Override 106 public Vector3d getRotationReferenceAxis() { 107 run(); 108 return referenceVector; 109 } 110 111 @Override 112 public Vector3d[] getPrincipalAxesOfInertia() { 113 run(); 114 return principalAxesOfInertia; 115 } 116 117 @Override 118 public Vector3d getDimension() { 119 run(); 120 Vector3d dimension = new Vector3d(); 121 dimension.sub(maxBoundary, minBoundary); 122 dimension.scale(0.5); 123 return dimension; 124 } 125 126 /** 127 * Returns the radius for drawing the minor rotation axis in the xy-plane 128 * @return double radius in xy-plane 129 */ 130 @Override 131 public double getRadius() { 132 run(); 133 return xyRadiusMax; 134 } 135 136 /** 137 * Returns a transformation matrix transform polyhedra for Cn structures. 138 * The center in this matrix is the geometric center, rather then the centroid. 139 * In Cn structures those are usually not the same. 140 * @return 141 */ 142 @Override 143 public Matrix4d getGeometicCenterTransformation() { 144 run(); 145 146 Matrix4d geometricCentered = new Matrix4d(reverseTransformationMatrix); 147 geometricCentered.setTranslation(new Vector3d(getGeometricCenter())); 148 149 return geometricCentered; 150 } 151 152 /** 153 * Returns the geometric center of polyhedron. In the case of the Cn 154 * point group, the centroid and geometric center are usually not 155 * identical. 156 * @return 157 */ 158 @Override 159 public Point3d getGeometricCenter() { 160 run(); 161 162 Point3d geometricCenter = new Point3d(); 163 Vector3d translation = new Vector3d(); 164 reverseTransformationMatrix.get(translation); 165 166 // calculate adjustment around z-axis and transform adjustment to 167 // original coordinate frame with the reverse transformation 168 if (rotationGroup.getPointGroup().startsWith("C")) { 169 Vector3d corr = new Vector3d(0,0, minBoundary.z+getDimension().z); 170 reverseTransformationMatrix.transform(corr); 171 geometricCenter.set(corr); 172 } 173 174 geometricCenter.add(translation); 175 return geometricCenter; 176 } 177 178 @Override 179 public Point3d getCentroid() { 180 return new Point3d(subunits.getCentroid()); 181 } 182 183 @Override 184 public Subunits getSubunits() { 185 return subunits; 186 } 187 188 public RotationGroup getRotationGroup() { 189 return rotationGroup; 190 } 191 192 @Override 193 public List<List<Integer>> getOrbits() { 194 return alignedOrbits; 195 } 196 197 /** 198 * @return 199 */ 200 201 private void run () { 202 if (modified) { 203 calcPrincipalRotationVector(); 204 calcPrincipalAxes(); 205 // initial alignment with draft reference axis 206 calcReferenceVector(); 207 calcTransformation(); 208 209 // refine ref. axis for cyclic and dihedral systems 210 if ((rotationGroup.getPointGroup().startsWith("C") && 211 !rotationGroup.getPointGroup().startsWith("C2")) || 212 (rotationGroup.getPointGroup().startsWith("D") && 213 !rotationGroup.getPointGroup().startsWith("D2")) 214 ) { 215 refineReferenceVector(); 216 calcTransformation(); 217 } 218 calcReverseTransformation(); 219 calcBoundaries(); 220 if (! rotationGroup.getPointGroup().equals("Helical")) { 221 calcAlignedOrbits(); 222 } 223 modified = false; 224 } 225 } 226 /** 227 * Returns a list of orbits (an orbit is a cyclic permutation of subunit indices that are related 228 * by a rotation around the principal rotation axis) ordered from the +z direction to the -z direction (z-depth). 229 * Within an orbit, subunit indices are ordered with a clockwise rotation around the z-axis. 230 * @return list of orbits ordered by z-depth 231 */ 232 private void calcAlignedOrbits() { 233 Map<Double, List<Integer>> depthMap = new TreeMap<Double, List<Integer>>(); 234 double[] depth = getSubunitZDepth(); 235 alignedOrbits = calcOrbits(); 236 237 // calculate the mean depth of orbit along z-axis 238 for (List<Integer> orbit: alignedOrbits) { 239 // calculate the mean depth along z-axis for each orbit 240 double meanDepth = 0; 241 for (int subunit: orbit) { 242 meanDepth += depth[subunit]; 243 } 244 meanDepth /= orbit.size(); 245 246 if (depthMap.get(meanDepth) != null) { 247 // System.out.println("Conflict in depthMap"); 248 meanDepth += 0.01; 249 } 250 depthMap.put(meanDepth, orbit); 251 } 252 253 // now fill orbits back into list ordered by depth 254 alignedOrbits.clear(); 255 for (List<Integer> orbit: depthMap.values()) { 256 // order subunit in a clockwise rotation around the z-axis 257 /// starting at the 12 O-clock position (+y position) 258 alignWithReferenceAxis(orbit); 259 alignedOrbits.add(orbit); 260 } 261 } 262 263 /** 264 * Returns an ordered list of subunit ids (orbit) in such a way that the subunit 265 * indices start at the 12 o-clock (+y axis) and proceed in a clockwise direction 266 * to the 11 o-clock position to close the "orbit". 267 * 268 * @param orbit list of subunit indices that are transformed into each other by a rotation 269 * @return list of subunit indices ordered in a clockwise direction 270 */ 271 272 private List<Integer> alignWithReferenceAxis(List<Integer> orbit) { 273 int n = subunits.getSubunitCount(); 274 int fold = rotationGroup.getRotation(0).getFold(); 275 if (fold < 2) { 276 return orbit; 277 } 278 Vector3d probe = new Vector3d(); 279 280 double dotMin1 = Double.MIN_VALUE; 281 double dotMin2 = Double.MIN_VALUE; 282 int index1 = 0; 283 int index2 = 0; 284 Vector3d Y1 = new Vector3d(0,1,0); 285 Vector3d Y2 = new Vector3d(0,1,0); 286 Matrix3d m = new Matrix3d(); 287 double angle = -2*Math.PI/fold; 288 m.rotZ(0.1*angle); // add small offset, since two subunits may be equidistant to the y-axis 289 m.transform(Y1); 290 m.rotZ(1.1*angle); 291 m.transform(Y2); 292 // transform subunit centers into z-aligned position and calculate 293 // width in xy direction. 294 for (int i: orbit) { 295 Point3d p = subunits.getCenters().get(i); 296 probe.set(p); 297 transformationMatrix.transform(probe); 298 // find subunit that lines up with y-axis 299 double dot1 = Y1.dot(probe); 300 if (dot1 > dotMin1) { 301 dotMin1 = dot1; 302 index1 = i; 303 } 304 // find next subunit (rotated by one fold around z-axis - clockwise) 305 double dot2 = Y2.dot(probe); 306 if (dot2 > dotMin2) { 307 dotMin2 = dot2; 308 index2 = i; 309 } 310 } 311// System.out.println("Index1/2: " + index1 + " - " + index2); 312// System.out.println("Orbit0: " + orbit); 313 // order subunit indices in a clockwise orientation around the z-axis 314 // bring subunit into position 0 315 for (int i = 0; i < n; i++) { 316 if (orbit.get(0) == index1) { 317 break; 318 } 319 Collections.rotate(orbit,1); 320 } 321// System.out.println("Orbit1: " + orbit); 322 // bring second subunit onto position 1 323 if (orbit.get(1) == index2) { 324 return orbit; 325 } 326 Collections.reverse(orbit.subList(1, orbit.size())); 327 if (orbit.get(1) != index2) { 328 LOGGER.warn("alignWithReferenceAxis failed"); 329 } 330// System.out.println("Orbit2: " + orbit); 331 return orbit; 332 } 333 334 335 336 private void calcTransformation() { 337 if (rotationGroup.getPointGroup().equals("C1")) { 338 calcTransformationByInertiaAxes(); 339 } else { 340 calcTransformationBySymmetryAxes(); 341 } 342 // make sure this value is zero. On Linux this value is 0. 343 transformationMatrix.setElement(3, 3, 1.0); 344 } 345 346 private void calcReverseTransformation() { 347 reverseTransformationMatrix.invert(transformationMatrix); 348 } 349 350 private void calcTransformationBySymmetryAxes() { 351 Vector3d[] axisVectors = new Vector3d[2]; 352 axisVectors[0] = new Vector3d(principalRotationVector); 353 axisVectors[1] = new Vector3d(referenceVector); 354 355 // y,z axis centered at the centroid of the subunits 356 Vector3d[] referenceVectors = new Vector3d[2]; 357 referenceVectors[0] = new Vector3d(Z_AXIS); 358 referenceVectors[1] = new Vector3d(Y_AXIS); 359 360 transformationMatrix = alignAxes(axisVectors, referenceVectors); 361 362 // combine with translation 363 Matrix4d combined = new Matrix4d(); 364 combined.setIdentity(); 365 Vector3d trans = new Vector3d(subunits.getCentroid()); 366 trans.negate(); 367 combined.setTranslation(trans); 368 transformationMatrix.mul(combined); 369 370 // for cyclic geometry, set a canonical view for the Z direction 371 if (rotationGroup.getPointGroup().startsWith("C")) { 372 calcZDirection(); 373 } 374 } 375 376 private void calcTransformationByInertiaAxes() { 377 Vector3d[] axisVectors = new Vector3d[2]; 378 axisVectors[0] = new Vector3d(principalAxesOfInertia[0]); 379 axisVectors[1] = new Vector3d(principalAxesOfInertia[1]); 380 381 Vector3d[] referenceVectors = new Vector3d[2]; 382 referenceVectors[0] = new Vector3d(Y_AXIS); 383 referenceVectors[1] = new Vector3d(X_AXIS); 384 385 // align inertia axes with y-x plane 386 transformationMatrix = alignAxes(axisVectors, referenceVectors); 387 388 // combine with translation 389 Matrix4d translation = new Matrix4d(); 390 translation.setIdentity(); 391 Vector3d trans = new Vector3d(subunits.getCentroid()); 392 trans.negate(); 393 translation.setTranslation(trans); 394 transformationMatrix.mul(translation); 395 } 396 397 /** 398 * Returns a transformation matrix that rotates refPoints to match 399 * coordPoints 400 * @param refPoints the points to be aligned 401 * @param referenceVectors 402 * @return 403 */ 404 private Matrix4d alignAxes(Vector3d[] axisVectors, Vector3d[] referenceVectors) { 405 Matrix4d m1 = new Matrix4d(); 406 AxisAngle4d a = new AxisAngle4d(); 407 Vector3d axis = new Vector3d(); 408 409 // calculate rotation matrix to rotate refPoints[0] into coordPoints[0] 410 Vector3d v1 = new Vector3d(axisVectors[0]); 411 Vector3d v2 = new Vector3d(referenceVectors[0]); 412 double dot = v1.dot(v2); 413 if (Math.abs(dot) < 0.999) { 414 axis.cross(v1,v2); 415 axis.normalize(); 416 a.set(axis, v1.angle(v2)); 417 m1.set(a); 418 // make sure matrix element m33 is 1.0. It's 0 on Linux. 419 m1.setElement(3, 3, 1.0); 420 } else if (dot > 0) { 421 // parallel axis, nothing to do -> identity matrix 422 m1.setIdentity(); 423 } else if (dot < 0) { 424 // anti-parallel axis, flip around x-axis 425 m1.set(flipX()); 426 } 427 428 // apply transformation matrix to all refPoints 429 m1.transform(axisVectors[0]); 430 m1.transform(axisVectors[1]); 431 432 // calculate rotation matrix to rotate refPoints[1] into coordPoints[1] 433 v1 = new Vector3d(axisVectors[1]); 434 v2 = new Vector3d(referenceVectors[1]); 435 Matrix4d m2 = new Matrix4d(); 436 dot = v1.dot(v2); 437 if (Math.abs(dot) < 0.999) { 438 axis.cross(v1,v2); 439 axis.normalize(); 440 a.set(axis, v1.angle(v2)); 441 m2.set(a); 442 // make sure matrix element m33 is 1.0. It's 0 on Linux. 443 m2.setElement(3, 3, 1.0); 444 } else if (dot > 0) { 445 // parallel axis, nothing to do -> identity matrix 446 m2.setIdentity(); 447 } else if (dot < 0) { 448 // anti-parallel axis, flip around z-axis 449 m2.set(flipZ()); 450 } 451 452 // apply transformation matrix to all refPoints 453 m2.transform(axisVectors[0]); 454 m2.transform(axisVectors[1]); 455 456 // combine the two rotation matrices 457 m2.mul(m1); 458 459 // the RMSD should be close to zero 460 Point3d[] axes = new Point3d[2]; 461 axes[0] = new Point3d(axisVectors[0]); 462 axes[1] = new Point3d(axisVectors[1]); 463 Point3d[] ref = new Point3d[2]; 464 ref[0] = new Point3d(referenceVectors[0]); 465 ref[1] = new Point3d(referenceVectors[1]); 466 if (SuperPosition.rmsd(axes, ref) > 0.1) { 467 LOGGER.info("AxisTransformation: axes alignment is off. RMSD: " + SuperPosition.rmsd(axes, ref)); 468 } 469 470 return m2; 471 } 472 473 private void calcPrincipalAxes() { 474 MomentsOfInertia moi = new MomentsOfInertia(); 475 476 for (Point3d[] list: subunits.getTraces()) { 477 for (Point3d p: list) { 478 moi.addPoint(p, 1.0); 479 } 480 } 481 principalAxesOfInertia = moi.getPrincipalAxes(); 482 } 483 484 /** 485 * Calculates the min and max boundaries of the structure after it has been 486 * transformed into its canonical orientation. 487 */ 488 private void calcBoundaries() { 489 minBoundary.x = Double.MAX_VALUE; 490 maxBoundary.x = Double.MIN_VALUE; 491 minBoundary.y = Double.MAX_VALUE; 492 maxBoundary.x = Double.MIN_VALUE; 493 minBoundary.z = Double.MAX_VALUE; 494 maxBoundary.z = Double.MIN_VALUE; 495 496 Point3d probe = new Point3d(); 497 498 for (Point3d[] list: subunits.getTraces()) { 499 for (Point3d p: list) { 500 probe.set(p); 501 transformationMatrix.transform(probe); 502 503 minBoundary.x = Math.min(minBoundary.x, probe.x); 504 maxBoundary.x = Math.max(maxBoundary.x, probe.x); 505 minBoundary.y = Math.min(minBoundary.y, probe.y); 506 maxBoundary.y = Math.max(maxBoundary.y, probe.y); 507 minBoundary.z = Math.min(minBoundary.z, probe.z); 508 maxBoundary.z = Math.max(maxBoundary.z, probe.z); 509 xyRadiusMax = Math.max(xyRadiusMax, Math.sqrt(probe.x*probe.x + probe.y * probe.y)); 510 } 511 } 512 } 513 514 /* 515 * Modifies the rotation part of the transformation axis for 516 * a Cn symmetric complex, so that the narrower end faces the 517 * viewer, and the wider end faces away from the viewer. Example: 3LSV 518 */ 519 private void calcZDirection() { 520 calcBoundaries(); 521 522 // if the longer part of the structure faces towards the back (-z direction), 523 // rotate around y-axis so the longer part faces the viewer (+z direction) 524 if (Math.abs(minBoundary.z) > Math.abs(maxBoundary.z)) { 525 Matrix4d rot = flipY(); 526 rot.mul(transformationMatrix); 527 transformationMatrix.set(rot); 528 } 529 } 530 531 /** 532 * 533 */ 534 private List<List<Integer>> getOrbitsByXYWidth() { 535 Map<Double, List<Integer>> widthMap = new TreeMap<Double, List<Integer>>(); 536 double[] width = getSubunitXYWidth(); 537 List<List<Integer>> orbits = calcOrbits(); 538 539 // calculate the mean width of orbits in XY-plane 540 for (List<Integer> orbit: orbits) { 541 double meanWidth = 0; 542 for (int subunit: orbit) { 543 meanWidth += width[subunit]; 544 } 545 meanWidth /= orbit.size(); 546 547 if (widthMap.get(meanWidth) != null) { 548 meanWidth += 0.01; 549 } 550 widthMap.put(meanWidth, orbit); 551 } 552 553 // now fill orbits back into list ordered by width 554 orbits.clear(); 555 for (List<Integer> orbit: widthMap.values()) { 556 orbits.add(orbit); 557 } 558 return orbits; 559 } 560 561 private double[] getSubunitXYWidth() { 562 int n = subunits.getSubunitCount(); 563 double[] width = new double[n]; 564 Point3d probe = new Point3d(); 565 566 // transform subunit centers into z-aligned position and calculate 567 // width in xy direction. 568 for (int i = 0; i < n; i++) { 569 width[i] = Double.MIN_VALUE; 570 for (Point3d p: subunits.getTraces().get(i)) { 571 probe.set(p); 572 transformationMatrix.transform(probe); 573 width[i] = Math.max(width[i], Math.sqrt(probe.x*probe.x + probe.y*probe.y)); 574 } 575 } 576 return width; 577 } 578 579 private double[] getSubunitZDepth() { 580 int n = subunits.getSubunitCount(); 581 double[] depth = new double[n]; 582 Point3d probe = new Point3d(); 583 584 // transform subunit centers into z-aligned position and calculate 585 // z-coordinates (depth) along the z-axis. 586 for (int i = 0; i < n; i++) { 587 Point3d p= subunits.getCenters().get(i); 588 probe.set(p); 589 transformationMatrix.transform(probe); 590 depth[i] = probe.z; 591 } 592 return depth; 593 } 594 595 /** 596 * Returns a list of list of subunit ids that form an "orbit", i.e. they 597 * are transformed into each other during a rotation around the principal symmetry axis (z-axis) 598 * @return 599 */ 600 private List<List<Integer>> calcOrbits() { 601 int n = subunits.getSubunitCount(); 602 int fold = rotationGroup.getRotation(0).getFold(); 603 604 List<List<Integer>> orbits = new ArrayList<List<Integer>>(); 605 boolean[] used = new boolean[n]; 606 Arrays.fill(used, false); 607 608 for (int i = 0; i < n; i++) { 609 if (! used[i]) { 610 // determine the equivalent subunits 611 List<Integer> orbit = new ArrayList<Integer>(fold); 612 for (int j = 0; j < fold; j++) { 613 List<Integer> permutation = rotationGroup.getRotation(j).getPermutation(); 614 orbit.add(permutation.get(i)); 615 used[permutation.get(i)] = true; 616 } 617 orbits.add(deconvolute(orbit)); 618 } 619 } 620 return orbits; 621 } 622 623 private List<Integer> deconvolute(List<Integer> orbit) { 624 if (rotationGroup.getOrder() < 2) { 625 return orbit; 626 } 627 List<Integer> p0 = rotationGroup.getRotation(0).getPermutation(); 628 List<Integer> p1 = rotationGroup.getRotation(1).getPermutation(); 629// System.out.println("deconvolute"); 630// System.out.println("Permutation0: " + p0); 631// System.out.println("Permutation1: " + p1); 632 633 List<Integer> inRotationOrder = new ArrayList<Integer>(orbit.size()); 634 inRotationOrder.add(orbit.get(0)); 635 for (int i = 1; i < orbit.size(); i++) { 636 int current = inRotationOrder.get(i-1); 637 int index = p0.indexOf(current); 638 int next = p1.get(index); 639 if (!orbit.contains(next)) { 640 LOGGER.warn("deconvolute: inconsistency in permuation. Returning original order"); 641 return orbit; 642 } 643 inRotationOrder.add(next); 644 } 645// System.out.println("In order: " + inRotationOrder); 646 return inRotationOrder; 647 } 648 649 /** 650 * Returns a vector along the principal rotation axis for the 651 * alignment of structures along the z-axis 652 * @return principal rotation vector 653 */ 654 private void calcPrincipalRotationVector() { 655 Rotation rotation = rotationGroup.getRotation(0); // the rotation around the principal axis is the first rotation 656 AxisAngle4d axisAngle = rotation.getAxisAngle(); 657 principalRotationVector = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z); 658 } 659 660 /** 661 * Returns a vector perpendicular to the principal rotation vector 662 * for the alignment of structures in the xy-plane 663 * @return reference vector 664 */ 665 private void calcReferenceVector() { 666 referenceVector = null; 667 if (rotationGroup.getPointGroup().startsWith("C")) { 668 referenceVector = getReferenceAxisCylic(); 669 } else if (rotationGroup.getPointGroup().startsWith("D")) { 670 referenceVector = getReferenceAxisDihedral(); 671 } else if (rotationGroup.getPointGroup().equals("T")) { 672 referenceVector = getReferenceAxisTetrahedral(); 673 } else if (rotationGroup.getPointGroup().equals("O")) { 674 referenceVector = getReferenceAxisOctahedral(); 675 } else if (rotationGroup.getPointGroup().equals("I")) { 676 referenceVector = getReferenceAxisIcosahedral(); 677 } else if (rotationGroup.getPointGroup().equals("Helical")) { 678 // TODO what should the reference vector be?? 679 referenceVector = getReferenceAxisCylic(); 680 } 681 682 if (referenceVector == null) { 683 LOGGER.warn("no reference vector found. Using y-axis."); 684 referenceVector = new Vector3d(Y_AXIS); 685 } 686 // make sure reference vector is perpendicular principal roation vector 687 referenceVector = orthogonalize(principalRotationVector, referenceVector); 688 } 689 690 /** 691 * Returns a normalized vector that represents a minor rotation axis, except 692 * for Cn, this represents an axis orthogonal to the principal axis. 693 * @return minor rotation axis 694 */ 695 private void refineReferenceVector() { 696 referenceVector = new Vector3d(Y_AXIS); 697 if (rotationGroup.getPointGroup().startsWith("C")) { 698 referenceVector = getReferenceAxisCylicWithSubunitAlignment(); 699 } else if (rotationGroup.getPointGroup().startsWith("D")) { 700 referenceVector = getReferenceAxisDihedralWithSubunitAlignment(); 701 } 702 703 referenceVector = orthogonalize(principalRotationVector, referenceVector); 704 } 705 706 private Vector3d orthogonalize(Vector3d vector1, Vector3d vector2) { 707 double dot = vector1.dot(vector2); 708 Vector3d ref = new Vector3d(vector2); 709// System.out.println("p.r: " + dot); 710// System.out.println("Orig refVector: " + referenceVector); 711 if (dot < 0) { 712 vector2.negate(); 713 } 714 vector2.cross(vector1, vector2); 715// System.out.println("Intermed. refVector: " + vector2); 716 vector2.normalize(); 717// referenceVector.cross(referenceVector, principalRotationVector); 718 vector2.cross(vector1, vector2); 719 vector2.normalize(); 720 if (ref.dot(vector2) < 0) { 721 vector2.negate(); 722 } 723// System.out.println("Mod. refVector: " + vector2); 724 return vector2; 725 } 726 /** 727 * Returns the default reference vector for the alignment of Cn structures 728 * @return 729 */ 730 private Vector3d getReferenceAxisCylic() { 731 if (rotationGroup.getPointGroup().equals("C2")) { 732 Vector3d vr = new Vector3d(subunits.getOriginalCenters().get(0)); 733 vr.sub(subunits.getCentroid()); 734 vr.normalize(); 735 return vr; 736 } 737 738 // get principal axis vector that is perpendicular to the principal 739 // rotation vector 740 Vector3d vmin = null; 741 double dotMin = 1.0; 742 for (Vector3d v: principalAxesOfInertia) { 743 if (Math.abs(principalRotationVector.dot(v)) < dotMin) { 744 dotMin = Math.abs(principalRotationVector.dot(v)); 745 vmin = new Vector3d(v); 746 } 747 } 748 if (principalRotationVector.dot(vmin) < 0) { 749 vmin.negate(); 750 } 751 752 return vmin; 753 } 754 755 756 /** 757 * Returns a reference vector for the alignment of Cn structures. 758 * @return reference vector 759 */ 760 private Vector3d getReferenceAxisCylicWithSubunitAlignment() { 761 if (rotationGroup.getPointGroup().equals("C2")) { 762 return referenceVector; 763 } 764 765 // find subunit that extends the most in the xy-plane 766 List<List<Integer>> orbits = getOrbitsByXYWidth(); 767 // get the last orbit which is the widest 768 List<Integer> widestOrbit = orbits.get(orbits.size()-1); 769 List<Point3d> centers = subunits.getCenters(); 770 int subunit = widestOrbit.get(0); 771 772 // calculate reference vector 773 Vector3d refAxis = new Vector3d(); 774 refAxis.sub(centers.get(subunit), subunits.getCentroid()); 775 refAxis.normalize(); 776 return refAxis; 777 } 778 779 /** 780 * 781 */ 782 private Vector3d getReferenceAxisDihedralWithSubunitAlignment() { 783 int maxFold = rotationGroup.getRotation(0).getFold(); 784 785 double minAngle = Double.MAX_VALUE; 786 Vector3d refVec = null; 787 788 Vector3d ref = getSubunitReferenceVector(); 789 790 for (int i = 0; i < rotationGroup.getOrder(); i++) { 791 if (rotationGroup.getRotation(i).getDirection() == 1 && 792 (rotationGroup.getRotation(i).getFold() < maxFold) || 793 rotationGroup.getPointGroup().equals("D2")) { 794 795 AxisAngle4d axisAngle = rotationGroup.getRotation(i).getAxisAngle(); 796 Vector3d v = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z); 797 v.normalize(); 798 799// System.out.println("Ref axis angle(+): " + Math.toDegrees(v.angle(ref))); 800 double angle = v.angle(ref); 801 if (angle < minAngle) { 802 minAngle = angle; 803 refVec = v; 804 } 805 Vector3d vn = new Vector3d(v); 806 vn.negate(); 807// System.out.println("Ref axis angle(-): " + Math.toDegrees(vn.angle(ref))); 808 angle = vn.angle(ref); 809 if (angle < minAngle) { 810 minAngle = angle; 811 refVec = vn; 812 } 813 } 814 } 815 refVec.normalize(); 816 return refVec; 817 } 818 819 /** 820 * 821 */ 822 private Vector3d getReferenceAxisDihedral() { 823 int maxFold = rotationGroup.getRotation(0).getFold(); 824 // one exception: D2 825 if (maxFold == 2) { 826 maxFold = 3; 827 } 828 // TODO how about D2, where minor axis = 2 = principal axis?? 829 for (int i = 0; i < rotationGroup.getOrder(); i++) { 830 if (rotationGroup.getRotation(i).getDirection() == 1 && rotationGroup.getRotation(i).getFold() < maxFold) { 831 AxisAngle4d axisAngle = rotationGroup.getRotation(i).getAxisAngle(); 832 Vector3d v = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z); 833 v.normalize(); 834 return v; 835 } 836 } 837 return null; 838 } 839 840 private Vector3d getReferenceAxisTetrahedral() { 841 for (int i = 0; i < rotationGroup.getOrder(); i++) { 842 AxisAngle4d axisAngle = rotationGroup.getRotation(i).getAxisAngle(); 843 Vector3d v = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z); 844 double d = v.dot(principalRotationVector); 845 if (rotationGroup.getRotation(i).getFold() == 3) { 846 // the dot product 0 is between to adjacent 3-fold axes 847 if (d > 0.3 && d < 0.9) { 848 return v; 849 } 850 } 851 } 852 return null; 853 } 854 855 private Vector3d getReferenceAxisOctahedral() { 856 for (int i = 0; i < rotationGroup.getOrder(); i++) { 857 AxisAngle4d axisAngle = rotationGroup.getRotation(i).getAxisAngle(); 858 Vector3d v = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z); 859 double d = v.dot(principalRotationVector); 860 if (rotationGroup.getRotation(i).getFold() == 4) { 861 // the dot product 0 is between to adjacent 4-fold axes 862 if (d > -0.1 && d < 0.1 ) { 863 return v; 864 } 865 } 866 } 867 return null; 868 } 869 870 private Vector3d getReferenceAxisIcosahedral() { 871 for (int i = 0; i < rotationGroup.getOrder(); i++) { 872 AxisAngle4d axisAngle = rotationGroup.getRotation(i).getAxisAngle(); 873 Vector3d v = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z); 874 double d = v.dot(principalRotationVector); 875 if (rotationGroup.getRotation(i).getFold() == 5) { 876 // the dot product of 0.447.. is between to adjacent 5-fold axes 877// if (d > 0.447 && d < 0.448) { 878 if (d > 0.4 && d < 0.5) { 879 return v; 880 } 881 } 882 } 883 return null; 884 } 885 886 private Vector3d getSubunitReferenceVector() { 887 int n = subunits.getSubunitCount(); 888 Point3d probe = new Point3d(); 889 890 // transform subunit centers into z-aligned position and calculate 891 // width in xy direction. 892 double maxWidthSq = 0; 893 Point3d ref = null; 894 for (int i = 0; i < n; i++) { 895 for (Point3d p: subunits.getTraces().get(i)) { 896 probe.set(p); 897 transformationMatrix.transform(probe); 898 double widthSq = probe.x*probe.x + probe.y*probe.y; 899 if (widthSq > maxWidthSq) { 900 maxWidthSq = widthSq; 901 ref = p; 902 } 903 } 904 } 905 // System.out.println("width: " + maxWidthSq); 906 Vector3d refVector = new Vector3d(); 907 refVector.sub(ref, subunits.getCentroid()); 908 refVector.normalize(); 909 return refVector; 910 } 911 912 private static Matrix4d flipX() { 913 Matrix4d rot = new Matrix4d(); 914 rot.m00 = 1; 915 rot.m11 = -1; 916 rot.m22 = -1; 917 rot.m33 = 1; 918 return rot; 919 } 920 921 private static Matrix4d flipY() { 922 Matrix4d rot = new Matrix4d(); 923 rot.m00 = -1; 924 rot.m11 = 1; 925 rot.m22 = -1; 926 rot.m33 = 1; 927 return rot; 928 } 929 930 private static Matrix4d flipZ() { 931 Matrix4d rot = new Matrix4d(); 932 rot.m00 = -1; 933 rot.m11 = -1; 934 rot.m22 = 1; 935 rot.m33 = 1; 936 return rot; 937 } 938 939}