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 */ 021 022package org.biojava.nbio.structure.symmetry.core; 023 024import javax.vecmath.AxisAngle4d; 025import javax.vecmath.Matrix4d; 026import javax.vecmath.Vector3d; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Comparator; 030import java.util.Iterator; 031import java.util.List; 032 033/** 034 * @see http://en.wikipedia.org/wiki/Rotation_group_SO(3) 035 * @author Peter 036 */ 037public class RotationGroup implements Iterable<Rotation> { 038 private List<Rotation> rotations = new ArrayList<Rotation>(); 039 private int principalAxisIndex = 0; 040 private int higherOrderRotationAxis = 0; 041 private int twoFoldsPerpendicular = 0; 042 private int highestOrder = 0; 043 private String pointGroup = "C1"; 044 private double symmetryDeviation = 0; 045 private boolean complete = true; 046 private boolean modified = true; 047 048 049 public int getOrder() { 050 return rotations.size(); 051 } 052 053 public Rotation getRotation(int index) { 054 return rotations.get(index); 055 } 056 057 public void addRotation(Rotation rotation) { 058 rotations.add(rotation); 059 modified = true; 060 } 061 062 public void setC1(int n) { 063 Rotation r = new Rotation(); 064 List<Integer> permutation = new ArrayList<Integer>(n); 065 for (int i = 0; i < n; i++) { 066 permutation.add(i); 067 } 068 r.setPermutation(permutation); 069 Matrix4d m = new Matrix4d(); 070 m.setIdentity(); 071 r.setTransformation(m); 072 r.setAxisAngle(new AxisAngle4d()); 073 r.setFold(1); 074 r.setScores(new QuatSymmetryScores()); 075 rotations.add(r); 076 pointGroup = "C1"; 077 } 078 079 public void removeRotation(int index) { 080 rotations.remove(index); 081 modified = true; 082 } 083 084 public void complete() { 085 if (modified) { 086 if (rotations.size() > 0) { 087 findHighestOrderAxis(); 088 setEAxis(); 089 calcAxesDirections(); 090 findHigherOrderAxes(); 091 findTwoFoldsPerpendicular(); 092 calcPointGroup(); 093 sortByFoldDecending(); 094 } 095 modified = false; 096 } 097 } 098 099 public String getPointGroup() { 100 if (modified) { 101 if (rotations.size() == 0) { 102 return "C1"; 103 } 104 complete(); 105 } 106 return pointGroup; 107 } 108 109 /** 110 * Returns QuatSymmetryScores averaged over all rotations 111 * (except the first rotation, which is the unit operation E) 112 * @return mean scores average over rotations 113 */ 114 public QuatSymmetryScores getScores() { 115 QuatSymmetryScores scores = new QuatSymmetryScores(); 116 117 int n = rotations.size()-1; 118 119 if (n > 0) { 120 double[] values = new double[n]; 121 122 // minRmsd 123 for (int i = 1; i < rotations.size(); i++) { 124 values[i-1] = rotations.get(i).getScores().getMinRmsd(); 125 } 126 scores.setMinRmsd(minScores(values)); 127 128 // maxRmsd 129 for (int i = 1; i < rotations.size(); i++) { 130 values[i-1] = rotations.get(i).getScores().getMaxRmsd(); 131 } 132 scores.setMaxRmsd(maxScores(values)); 133 134 // Rmsd 135 for (int i = 1; i < rotations.size(); i++) { 136 values[i-1] = rotations.get(i).getScores().getRmsd(); 137 } 138 scores.setRmsd(averageScores(values)); 139 140 // minTm 141 for (int i = 1; i < rotations.size(); i++) { 142 values[i-1] = rotations.get(i).getScores().getMinTm(); 143 } 144 scores.setMinTm(minScores(values)); 145 146 // maxTm 147 for (int i = 1; i < rotations.size(); i++) { 148 values[i-1] = rotations.get(i).getScores().getMaxTm(); 149 } 150 scores.setMaxTm(maxScores(values)); 151 152 // Tm 153 for (int i = 1; i < rotations.size(); i++) { 154 values[i-1] = rotations.get(i).getScores().getTm(); 155 } 156 scores.setTm(averageScores(values)); 157 158 // Rmsd subunit centers 159 for (int i = 1; i < rotations.size(); i++) { 160 values[i-1] = rotations.get(i).getScores().getRmsdCenters(); 161 } 162 scores.setRmsdCenters(averageScores(values)); 163 // TmIntra 164 for (int i = 1; i < rotations.size(); i++) { 165 values[i-1] = rotations.get(i).getScores().getTmIntra(); 166 } 167 scores.setTmIntra(averageScores(values)); 168 169 // RmsdIntra 170 for (int i = 1; i < rotations.size(); i++) { 171 values[i-1] = rotations.get(i).getScores().getRmsdIntra(); 172 } 173 scores.setRmsdIntra(averageScores(values)); 174 175 // SymDeviation 176 scores.setSymDeviation(symmetryDeviation); 177 } 178 return scores; 179 } 180 181 /** 182 * @param symmetryDeviation the symmetryDeviation to set 183 */ 184 public void setSymmetryDeviation(double symmetryDeviation) { 185 this.symmetryDeviation = symmetryDeviation; 186 } 187 188 public boolean isComplete() { 189 return complete; 190 } 191 192 @Override 193 public String toString() { 194 StringBuilder sb = new StringBuilder(); 195 sb.append("Rotations: " + rotations.size() + "\n"); 196 for (Rotation s: rotations) { 197 sb.append(s.toString()).append("\n"); 198 } 199 return sb.toString(); 200 } 201 202 private double averageScores(double[] scores) { 203 double sum = 0; 204 for (double s: scores) { 205 sum += s; 206 } 207 return sum/scores.length; 208 } 209 210 private double minScores(double[] scores) { 211 double score = Double.MAX_VALUE; 212 for (double s: scores) { 213 score = Math.min(score, s); 214 } 215 return score; 216 } 217 218 private double maxScores(double[] scores) { 219 double score = Double.MIN_VALUE; 220 for (double s: scores) { 221 score = Math.max(score, s); 222 } 223 return score; 224 } 225 226 private void findHighestOrderAxis() { 227 highestOrder = 1; 228 principalAxisIndex = 0; 229 double rmsd = Double.MAX_VALUE; 230 231 for (int i = 0; i < rotations.size(); i++) { 232 Rotation s = rotations.get(i); 233 if (s.getFold() > highestOrder) { 234 highestOrder = s.getFold(); 235 principalAxisIndex = i; 236 rmsd = s.getTraceRmsd(); 237 } else if (s.getFold() >= highestOrder && s.getTraceRmsd() < rmsd) { 238 highestOrder = s.getFold(); 239 principalAxisIndex = i; 240 rmsd = s.getTraceRmsd(); 241 } 242 } 243 } 244 245 /** 246 * Add E operation to the highest order rotation axis. By definition 247 * E belongs to the highest order axis. 248 */ 249 private void setEAxis() { 250 Rotation e = rotations.get(0); 251 Rotation h = rotations.get(principalAxisIndex); 252 e.setAxisAngle(new AxisAngle4d(h.getAxisAngle())); 253 e.getAxisAngle().angle = 0.0; 254 e.setFold(h.getFold()); 255 } 256 257 private void findHigherOrderAxes() { 258 higherOrderRotationAxis = 0; 259 for (Rotation s: rotations) { 260 if (s.getFold() > 2 && s.getDirection() == 1) { 261 higherOrderRotationAxis++; 262 } 263 } 264 } 265 266 private void calcAxesDirections() { 267 if (highestOrder == 1) { 268 for (Rotation s: rotations) { 269 s.setDirection(0); 270 } 271 return; 272 } 273 274 AxisAngle4d pa = rotations.get(principalAxisIndex).getAxisAngle(); 275 Vector3d pv = new Vector3d(pa.x, pa.y, pa.z); 276 277 for (Rotation s: rotations) { 278 AxisAngle4d axis = s.getAxisAngle(); 279 Vector3d av = new Vector3d(axis.x, axis.y, axis.z); 280 if (Math.abs(pv.dot(av)) > 0.9f) { 281 // co-linear with principal axis 282 s.setDirection(0); 283 } else { 284 // not co-linear or perpendicular to principal axis 285 s.setDirection(1); 286 } 287 } 288 rotations.get(0).setDirection(0); // set the E axis to the principal axis (by definition) 289 } 290 291 private void findTwoFoldsPerpendicular() { 292 twoFoldsPerpendicular = 0; 293 for (Rotation s: rotations) { 294 if (s.getFold() == 2 && s.getDirection() == 1) { 295 twoFoldsPerpendicular++; 296 } 297 } 298 } 299 300 301 public int getHigherOrderRotationAxis(){ 302 return higherOrderRotationAxis; 303 } 304 305 public int getTwoFoldsPerpendicular(){ 306 return twoFoldsPerpendicular; 307 } 308 309 public int getPrincipalAxisIndex(){ 310 return principalAxisIndex; 311 } 312 313 private void calcPointGroup() { 314 complete = false; 315 if (higherOrderRotationAxis > 1) { 316 // cubic groups 317 if (highestOrder == 5) { 318 // rotational icosahedral symmetry or chiral icosahedral symmetry 319 pointGroup = "I"; 320 complete = rotations.size() == 60; 321 } else if (highestOrder == 4) { 322 // rotational octahedral symmetry or chiral octahedral symmetry 323 pointGroup = "O"; 324 complete = rotations.size() == 24; 325 } else if (highestOrder == 3) { 326 // rotational tetrahedral symmetry or chiral tetrahedral symmetry 327 pointGroup = "T"; 328 complete = rotations.size() == 12; 329 } 330 } else { 331 // Cn and Dn groups 332 // if E is not counted, subtract 1 333 if (Math.abs(twoFoldsPerpendicular - highestOrder) <= 1 && highestOrder > 1) { 334 pointGroup = "D" + highestOrder; 335 complete = rotations.size() == 2 * highestOrder; 336 } else { 337 pointGroup = "C" + highestOrder; 338 complete = rotations.size() == highestOrder; 339 } 340 } 341 342 if (!complete) { 343 // the rotation group is incomplete, remove partial results. This happens 344 // when a structure is symmetric, some subunits are below the rmsd threshold, 345 // and some are just above the rmsd threshold 346 int n = 0; 347 if (rotations.size() > 0) { 348 n = rotations.get(0).getPermutation().size(); 349 rotations.clear(); 350 } 351 setC1(n); 352 } 353 } 354 355 public void sortByFoldDecending() { 356 Collections.sort(rotations, new Comparator<Rotation>() { 357 @Override 358 public int compare(Rotation o1, Rotation o2) { 359 int delta = o1.getDirection() - o2.getDirection(); 360 if (delta != 0) { 361 return delta; 362 } 363 delta = Math.round(Math.signum(o2.getFold() - o1.getFold())); 364 if (delta != 0) { 365 return delta; 366 } 367 368 delta = (int)(Math.signum(o1.getAxisAngle().angle - o2.getAxisAngle().angle)); 369 return delta; 370 } 371 }); 372 } 373 374 @Override 375 public Iterator<Rotation> iterator() { 376 return rotations.iterator(); 377 } 378}