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.internal; 022 023import java.util.ArrayList; 024import java.util.List; 025 026import javax.vecmath.Matrix4d; 027 028import org.biojava.nbio.structure.Atom; 029import org.biojava.nbio.structure.Structure; 030import org.biojava.nbio.structure.StructureException; 031import org.biojava.nbio.structure.StructureIdentifier; 032import org.biojava.nbio.structure.StructureTools; 033import org.biojava.nbio.structure.align.ce.CECalculator; 034import org.biojava.nbio.structure.align.ce.CeCPMain; 035import org.biojava.nbio.structure.align.ce.MatrixListener; 036import org.biojava.nbio.structure.align.model.AFPChain; 037import org.biojava.nbio.structure.align.multiple.MultipleAlignment; 038import org.biojava.nbio.structure.align.util.AFPChainScorer; 039import org.biojava.nbio.structure.jama.Matrix; 040import org.biojava.nbio.structure.secstruc.SecStrucCalc; 041import org.biojava.nbio.structure.secstruc.SecStrucTools; 042import org.biojava.nbio.structure.symmetry.internal.CESymmParameters.SymmetryType; 043import org.biojava.nbio.structure.symmetry.utils.SymmetryTools; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047/** 048 * Identify the symmetries in a structure by running an alignment of the 049 * structure against itself disabling the diagonal of the identity alignment. 050 * <p> 051 * Iterating over previous results and disabling the diagonal of the previous 052 * alignments can also be done with this implementation, which will generate a 053 * set of self-alignments (disabled, because none improvements were shown, but 054 * can be turn on manually). 055 * <p> 056 * Multiple levels of symmetry can be analyzed by finding symmetries in repeats 057 * of previous results. This feature allows to find multiple symmetry axes. 058 * <p> 059 * The alignment is then refined to obtain a consistent alignment among all 060 * residues of the structure and organized into different parts, called 061 * symmetric repeats. 062 * <p> 063 * After refinement of the initial alignment, an optimization step can be used 064 * to improve the overall score of the repeat multiple alignment. 065 * 066 * @author Andreas Prlic 067 * @author Spencer Bliven 068 * @author Aleix Lafita 069 * @since 4.1.1 070 * 071 */ 072public class CeSymm { 073 074 /** 075 * Version History: 076 * <p> 077 * <ul> 078 * <li>1.0 - initial implementation of CE-Symm. 079 * <li>1.1 - enable multiple CE-Symm runs to calculate all self-alignments. 080 * <li>2.0 - refine the alignment for consistency of repeat definition. 081 * <li>2.1 - optimize the alignment to improve the score. 082 * <li>2.2 - run multiple symmetry levels recursively to find PG and 083 * hierarchical symmetries. 084 * </ul> 085 */ 086 public static final String version = "2.2"; 087 public static final String algorithmName = "jCE-symm"; 088 private static final Logger logger = LoggerFactory.getLogger(CeSymm.class); 089 private final static boolean multiPass = false; // multiple self-alignments 090 091 /** 092 * Prevent instantiation. Static class. 093 */ 094 private CeSymm() { 095 } 096 097 private static Matrix align(AFPChain afpChain, Atom[] ca1, Atom[] ca2, 098 CESymmParameters params, Matrix origM, CECalculator calculator, 099 int counter) throws StructureException { 100 101 int fragmentLength = params.getWinSize(); 102 Atom[] ca2clone = StructureTools.cloneAtomArray(ca2); 103 104 int rows = ca1.length; 105 int cols = ca2.length; 106 107 // Matrix that tracks similarity of a fragment of length fragmentLength 108 // starting a position i,j. 109 110 int blankWindowSize = fragmentLength; 111 if (origM == null) { 112 113 // Build alignment ca1 to ca2-ca2 114 afpChain = calculator.extractFragments(afpChain, ca1, ca2clone); 115 116 origM = SymmetryTools.blankOutPreviousAlignment(afpChain, ca2, 117 rows, cols, calculator, null, blankWindowSize); 118 119 } else { 120 // we are doing an iteration on a previous alignment 121 // mask the previous alignment 122 origM = SymmetryTools.blankOutPreviousAlignment(afpChain, ca2, 123 rows, cols, calculator, origM, blankWindowSize); 124 } 125 126 Matrix clone = (Matrix) origM.clone(); 127 128 // that's the matrix to run the alignment on.. 129 calculator.setMatMatrix(clone.getArray()); 130 131 calculator.traceFragmentMatrix(afpChain, ca1, ca2clone); 132 133 final Matrix origMfinal = (Matrix) origM.clone(); 134 // Add a matrix listener to keep the blacked zones in max. 135 calculator.addMatrixListener(new MatrixListener() { 136 137 @Override 138 public double[][] matrixInOptimizer(double[][] max) { 139 140 // Check every entry of origM for blacked out regions 141 for (int i = 0; i < max.length; i++) { 142 for (int j = 0; j < max[i].length; j++) { 143 if (origMfinal.getArray()[i][j] > 1e9) { 144 max[i][j] = -origMfinal.getArray()[i][j]; 145 } 146 } 147 } 148 return max; 149 } 150 151 @Override 152 public boolean[][] initializeBreakFlag(boolean[][] brkFlag) { 153 154 return brkFlag; 155 } 156 }); 157 158 calculator.nextStep(afpChain, ca1, ca2clone); 159 160 afpChain.setAlgorithmName(algorithmName); 161 afpChain.setVersion(version); 162 163 afpChain.setDistanceMatrix(origM); 164 165 return origMfinal; 166 167 } 168 169 @SuppressWarnings("unused") 170 protected static CeSymmResult align(Atom[] atoms, CESymmParameters params) 171 throws StructureException { 172 173 CeSymmResult result = new CeSymmResult(); 174 result.setParams(params); 175 result.setAtoms(atoms); 176 177 // STEP 1: prepare all the information for the symmetry alignment 178 Atom[] ca2 = StructureTools.duplicateCA2(atoms); 179 int rows = atoms.length; 180 int cols = ca2.length; 181 182 if (rows == 0 || cols == 0) { 183 throw new StructureException("Aligning empty structure"); 184 } 185 186 Matrix origM = null; 187 AFPChain myAFP = new AFPChain(algorithmName); 188 CECalculator calculator = new CECalculator(params); 189 Matrix lastMatrix = null; 190 191 List<AFPChain> selfAlignments = new ArrayList<>(); 192 AFPChain optimalAFP = null; 193 194 // STEP 2: perform the self-alignments of the structure 195 int i = 0; 196 do { 197 if (origM != null) 198 myAFP.setDistanceMatrix((Matrix) origM.clone()); 199 200 origM = align(myAFP, atoms, ca2, params, origM, calculator, i); 201 202 double tmScore2 = AFPChainScorer.getTMScore(myAFP, atoms, ca2); 203 myAFP.setTMScore(tmScore2); 204 205 AFPChain newAFP = (AFPChain) myAFP.clone(); 206 newAFP = CeCPMain.postProcessAlignment(newAFP, atoms, ca2, 207 calculator); 208 209 // Calculate and set the TM score for the newAFP alignment 210 double tmScore3 = AFPChainScorer.getTMScore(newAFP, atoms, ca2); 211 newAFP.setTMScore(tmScore3); 212 213 // Determine if the alignment is significant, stop if false 214 if (tmScore3 < params.getUnrefinedScoreThreshold()) { 215 // If it is the first alignment save it anyway 216 if (i == 0) 217 selfAlignments.add(newAFP); 218 // store final matrix 219 lastMatrix = newAFP.getDistanceMatrix().copy(); 220 break; 221 } 222 223 // If it is a symmetric alignment add it to the allAlignments list 224 selfAlignments.add(newAFP); 225 226 i++; 227 228 } while (i < params.getMaxSymmOrder() && multiPass); 229 230 // We reached the maximum order, so blank out the final alignment 231 if (lastMatrix == null && selfAlignments.size() > 1 && multiPass) { 232 AFPChain last = selfAlignments.get(selfAlignments.size() - 1); 233 lastMatrix = SymmetryTools.blankOutPreviousAlignment(last, ca2, 234 last.getCa1Length(), last.getCa2Length(), calculator, 235 origM, params.getWinSize()); 236 lastMatrix = lastMatrix.getMatrix(0, last.getCa1Length() - 1, 0, 237 last.getCa2Length() - 1); 238 } 239 240 // Extract the structure identifier 241 optimalAFP = selfAlignments.get(0); 242 StructureIdentifier id = atoms[0].getGroup().getChain().getStructure() 243 .getStructureIdentifier(); 244 if(id != null) { 245 optimalAFP.setName1(id.getIdentifier()); 246 optimalAFP.setName2(id.getIdentifier()); 247 } 248 249 // Store the optimal self-alignment 250 result.setSelfAlignment(optimalAFP); 251 result.setStructureId(id); 252 253 // Determine the symmetry Type or get the one in params 254 SymmetryType type = params.getSymmType(); 255 if (type == SymmetryType.AUTO) { 256 if (result.getSelfAlignment().getBlockNum() == 1) { 257 type = SymmetryType.OPEN; 258 logger.info("Open Symmetry detected"); 259 } else { 260 type = SymmetryType.CLOSED; 261 logger.info("Close Symmetry detected"); 262 } 263 } 264 265 // Do not try the refinement if the self-alignment is not significant 266 if (optimalAFP.getTMScore() < params.getUnrefinedScoreThreshold()){ 267 result.setNumRepeats(1); 268 return result; 269 } 270 271 // STEP 3: order detection & symmetry refinement, apply consistency 272 try { 273 // ORDER DETECTION 274 OrderDetector orderDetector = null; 275 int order = 1; 276 switch (params.getOrderDetectorMethod()) { 277 case USER_INPUT: 278 order = params.getUserOrder(); 279 break; 280 case SEQUENCE_FUNCTION: 281 // Does not work for OPEN alignments 282 if (type == SymmetryType.CLOSED) { 283 orderDetector = new SequenceFunctionOrderDetector( 284 params.getMaxSymmOrder(), 0.4f); 285 order = orderDetector.calculateOrder( 286 result.getSelfAlignment(), atoms); 287 break; 288 } 289 case ANGLE: 290 // Does not work for OPEN alignments 291 if (type == SymmetryType.CLOSED) { 292 orderDetector = new AngleOrderDetectorPlus( 293 params.getMaxSymmOrder()); 294 order = orderDetector.calculateOrder( 295 result.getSelfAlignment(), atoms); 296 break; 297 } 298 case GRAPH_COMPONENT: 299 orderDetector = new GraphComponentOrderDetector(); 300 order = orderDetector.calculateOrder(result.getSelfAlignment(), 301 atoms); 302 break; 303 } 304 result.setNumRepeats(order); 305 306 // REFINEMENT 307 SymmetryRefiner refiner = null; 308 switch (params.getRefineMethod()) { 309 case NOT_REFINED: 310 return result; 311 case SEQUENCE_FUNCTION: 312 // Does not work for OPEN alignments 313 if (type == SymmetryType.CLOSED) { 314 refiner = new SequenceFunctionRefiner(); 315 break; 316 } 317 case GRAPH_COMPONENT: 318 refiner = new GraphComponentRefiner(); 319 break; 320 } 321 322 MultipleAlignment msa = refiner.refine(result.getSelfAlignment(), 323 atoms, order); 324 325 // Refinement succeeded, store results 326 result.setMultipleAlignment(msa); 327 result.setNumRepeats(msa.size()); 328 result.setRefined(true); 329 330 } catch (RefinerFailedException e) { 331 logger.info("Refinement failed: " + e.getMessage()); 332 return result; 333 } 334 335 // STEP 4: symmetry axes 336 SymmetryAxes axes = new SymmetryAxes(); 337 int order = result.getMultipleAlignment().size(); 338 Matrix4d axis = result.getMultipleAlignment().getBlockSet(0) 339 .getTransformations().get(1); 340 axes.addAxis(axis, order, type); 341 342 result.setAxes(axes); 343 return result; 344 } 345 346 /** 347 * Analyze the symmetries of the input Atom array using the DEFAULT 348 * parameters. 349 * 350 * @param atoms 351 * representative Atom array of the Structure 352 * @return CeSymmResult 353 * @throws StructureException 354 */ 355 public static CeSymmResult analyze(Atom[] atoms) throws StructureException { 356 CESymmParameters params = new CESymmParameters(); 357 return analyze(atoms, params); 358 } 359 360 /** 361 * Analyze the symmetries of the input Atom array using the provided 362 * parameters. 363 * 364 * @param atoms 365 * representative Atom array of the Structure 366 * @param params 367 * CeSymmParameters bean 368 * @return CeSymmResult 369 * @throws StructureException 370 */ 371 public static CeSymmResult analyze(Atom[] atoms, CESymmParameters params) 372 throws StructureException { 373 374 if (atoms.length < 1) 375 throw new IllegalArgumentException("Empty Atom array given."); 376 377 // If the SSE information is needed, we calculate it if the user did not 378 if (params.getSSEThreshold() > 0) { 379 Structure s = atoms[0].getGroup().getChain().getStructure(); 380 if (SecStrucTools.getSecStrucInfo(s).isEmpty()) { 381 logger.info("Calculating Secondary Structure..."); 382 SecStrucCalc ssp = new SecStrucCalc(); 383 ssp.calculate(s, true); 384 } 385 } 386 387 CeSymmIterative iter = new CeSymmIterative(params); 388 CeSymmResult result = iter.execute(atoms); 389 390 if (result.isRefined()) { 391 // Optimize the global alignment freely once more (final step) 392 if (params.getOptimization() && result.getSymmLevels() > 1) { 393 try { 394 SymmOptimizer optimizer = new SymmOptimizer(result); 395 MultipleAlignment optimized = optimizer.optimize(); 396 // Set the optimized MultipleAlignment and the axes 397 result.setMultipleAlignment(optimized); 398 } catch (RefinerFailedException e) { 399 logger.info("Final optimization failed:" + e.getMessage()); 400 } 401 } 402 result.getMultipleAlignment().getEnsemble() 403 .setStructureIdentifiers(result.getRepeatsID()); 404 } 405 return result; 406 } 407 408 /** 409 * Analyze a single level of symmetry. 410 * 411 * @param atoms 412 * Atom array of the current level 413 * @return CeSymmResult 414 * @throws StructureException 415 */ 416 public static CeSymmResult analyzeLevel(Atom[] atoms, 417 CESymmParameters params) throws StructureException { 418 419 if (atoms.length < 1) 420 throw new IllegalArgumentException("Empty Atom array given."); 421 422 CeSymmResult result = align(atoms, params); 423 424 if (result.isRefined()) { 425 // STEP 5: symmetry alignment optimization 426 if (result.getParams().getOptimization()) { 427 try { 428 MultipleAlignment msa = result.getMultipleAlignment(); 429 SymmOptimizer optimizer = new SymmOptimizer(result); 430 msa = optimizer.optimize(); 431 result.setMultipleAlignment(msa); 432 } catch (RefinerFailedException e) { 433 logger.debug("Optimization failed:{}", e.getMessage()); 434 } 435 } 436 } 437 return result; 438 } 439 440}