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.Arrays; 025import java.util.Collections; 026import java.util.List; 027import java.util.Set; 028 029import javax.vecmath.Matrix4d; 030 031import org.biojava.nbio.structure.Atom; 032import org.biojava.nbio.structure.StructureException; 033import org.biojava.nbio.structure.StructureIdentifier; 034import org.biojava.nbio.structure.align.multiple.Block; 035import org.biojava.nbio.structure.align.multiple.BlockImpl; 036import org.biojava.nbio.structure.align.multiple.BlockSet; 037import org.biojava.nbio.structure.align.multiple.BlockSetImpl; 038import org.biojava.nbio.structure.align.multiple.MultipleAlignment; 039import org.biojava.nbio.structure.align.multiple.MultipleAlignmentImpl; 040import org.biojava.nbio.structure.align.multiple.util.MultipleAlignmentScorer; 041import org.biojava.nbio.structure.secstruc.SecStrucElement; 042import org.biojava.nbio.structure.secstruc.SecStrucTools; 043import org.biojava.nbio.structure.secstruc.SecStrucType; 044import org.biojava.nbio.structure.symmetry.internal.CESymmParameters.RefineMethod; 045import org.biojava.nbio.structure.symmetry.internal.CESymmParameters.SymmetryType; 046import org.biojava.nbio.structure.symmetry.utils.SymmetryTools; 047import org.jgrapht.Graph; 048import org.jgrapht.alg.ConnectivityInspector; 049import org.jgrapht.graph.DefaultEdge; 050import org.jgrapht.graph.SimpleGraph; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * Iterative version of CeSymm that aims at identifying all symmetry axis of a 056 * structure. 057 * <p> 058 * Works in the following way: 059 * <ul> 060 * <li>Run CeSymm on the original structure. 061 * <li>Calculate the symmetric unit boundaries. 062 * <li>Run CeSymm on one of the symmetric units to find further symmetries. 063 * <li>Repeat the last two steps until no more significant results are found. 064 * <li>Map back all residues in a multiple alignment of the repeats. 065 * </ul> 066 * </li> 067 * 068 * @author Aleix Lafita 069 * @since 4.1.1 070 * 071 */ 072public class CeSymmIterative { 073 074 private static final Logger logger = LoggerFactory 075 .getLogger(CeSymmIterative.class); 076 077 private CESymmParameters params; 078 private Graph<Integer, DefaultEdge> alignGraph; // cumulative 079 private List<CeSymmResult> levels; // symmetry at each level 080 081 /** 082 * For the iterative algorithm to work properly the refinement and 083 * optimization options should be turned on, because the alignment has to be 084 * consistent at every recursive step. 085 * 086 * @param param 087 * CeSymm parameters, make sure they are cloned 088 */ 089 public CeSymmIterative(CESymmParameters param) { 090 params = param; 091 alignGraph = new SimpleGraph<Integer, DefaultEdge>(DefaultEdge.class); 092 levels = new ArrayList<CeSymmResult>(); 093 } 094 095 /** 096 * This method uses iteratively CeSymm to calculate all symmetries in the 097 * input array of atoms and organize them in a multiple alignment of the 098 * repeats. 099 * 100 * @param atoms 101 * atoms 102 * @return CeSymmResult 103 * 104 * @throws StructureException 105 */ 106 public CeSymmResult execute(Atom[] atoms) throws StructureException { 107 108 // First iterate through all levels and then reconstruct all repeats 109 iterate(atoms); 110 return reconstructSymmResult(atoms); 111 112 } 113 114 /** 115 * This method runs iteratively the analysis of one level of symmetry with 116 * CeSymm on the input Atom array until no more symmetries exist. 117 * 118 * @param atoms 119 * representative Atom array of the Structure 120 * @return true if any symmetry was found, false if asymmetric 121 * @throws StructureException 122 */ 123 private void iterate(Atom[] atoms) throws StructureException { 124 125 logger.debug("Starting new iteration..."); 126 127 // Return if the Atom array is too short 128 if ((atoms.length <= params.getWinSize() 129 || atoms.length <= params.getMinCoreLength()) 130 && !levels.isEmpty()) { 131 logger.debug("Aborting iteration due to insufficient Atom " 132 + "array length: %d", atoms.length); 133 return; 134 } 135 136 // Return if the maximum levels of symmetry have been reached 137 if (params.getSymmLevels() > 0) { 138 if (levels.size() == params.getSymmLevels()) 139 return; 140 } 141 142 // Perform one level CeSymm alignment 143 CeSymmResult result = CeSymm.analyzeLevel(atoms, params); 144 145 if (params.getRefineMethod() == RefineMethod.NOT_REFINED 146 || !result.isSignificant()) { 147 if (levels.isEmpty()) 148 levels.add(result); 149 return; 150 } 151 152 // Generate the Atoms of one of the symmetric repeat 153 Integer start = null; 154 int it = 0; 155 while (start == null) { 156 start = result.getMultipleAlignment().getBlocks().get(0) 157 .getAlignRes().get(0).get(it); 158 it++; 159 } 160 Integer end = null; 161 it = result.getMultipleAlignment().getBlocks().get(0).getAlignRes() 162 .get(0).size() - 1; 163 while (end == null) { 164 end = result.getMultipleAlignment().getBlocks().get(0) 165 .getAlignRes().get(0).get(it); 166 it--; 167 } 168 Atom[] atomsR = Arrays.copyOfRange(atoms, start, end + 1); 169 170 // Check the SSE requirement 171 if (countHelixStrandSSE(atomsR) < params.getSSEThreshold()) { 172 if (levels.isEmpty()) 173 levels.add(result); 174 return; 175 } 176 177 // If symmetric store the residue dependencies in alignment graph 178 Block b = result.getMultipleAlignment().getBlock(0); 179 for (int pos = 0; pos < b.length(); pos++) { 180 for (int su = 0; su < b.size() - 1; su++) { 181 Integer pos1 = b.getAlignRes().get(su).get(pos); 182 Integer pos2 = b.getAlignRes().get(su + 1).get(pos); 183 // Add edge from lower to higher positions 184 if (pos1 != null && pos2 != null) { 185 alignGraph.addVertex(pos1); 186 alignGraph.addVertex(pos2); 187 alignGraph.addEdge(pos1, pos2); 188 } 189 } 190 } 191 192 // Iterate further on those Atoms (of the first repeat only) 193 levels.add(result); 194 iterate(atomsR); 195 } 196 197 /** 198 * After all the analysis iterations have finished, the final Result object 199 * is reconstructed using the cumulative alignment graph. 200 * 201 * @param atoms 202 * the original structure atoms 203 * @return CeSymmResult reconstructed symmetry result 204 * @throws StructureException 205 */ 206 private CeSymmResult reconstructSymmResult(Atom[] atoms) 207 throws StructureException { 208 209 // If one level, nothing to build or calculate 210 if (levels.size() == 1) 211 return levels.get(0); 212 213 CeSymmResult result = new CeSymmResult(); 214 result.setSelfAlignment(levels.get(0).getSelfAlignment()); 215 result.setStructureId(levels.get(0).getStructureId()); 216 result.setAtoms(levels.get(0).getAtoms()); 217 result.setParams(levels.get(0).getParams()); 218 219 // Initialize a new multiple alignment 220 MultipleAlignment msa = new MultipleAlignmentImpl(); 221 msa.getEnsemble().setAtomArrays(new ArrayList<Atom[]>()); 222 msa.getEnsemble().setStructureIdentifiers( 223 new ArrayList<StructureIdentifier>()); 224 msa.getEnsemble().setAlgorithmName(CeSymm.algorithmName); 225 msa.getEnsemble().setVersion(CeSymm.version); 226 227 BlockSet bs = new BlockSetImpl(msa); 228 Block b = new BlockImpl(bs); 229 b.setAlignRes(new ArrayList<List<Integer>>()); 230 231 // Calculate the connected groups of the alignment graph 232 ConnectivityInspector<Integer, DefaultEdge> inspector = new ConnectivityInspector<Integer, DefaultEdge>( 233 alignGraph); 234 List<Set<Integer>> comps = inspector.connectedSets(); 235 List<ResidueGroup> groups = new ArrayList<ResidueGroup>(comps.size()); 236 for (Set<Integer> comp : comps) 237 groups.add(new ResidueGroup(comp)); 238 239 // Calculate the total number of repeats 240 int order = 1; 241 for (CeSymmResult sr : levels) 242 order *= sr.getMultipleAlignment().size(); 243 for (int su = 0; su < order; su++) 244 b.getAlignRes().add(new ArrayList<Integer>()); 245 246 // Construct the resulting MultipleAlignment from ResidueGroups 247 for (ResidueGroup group : groups) { 248 if (group.order() != order) 249 continue; 250 group.combineWith(b.getAlignRes()); 251 } 252 253 // The reconstruction failed, so the top level is returned 254 if (b.length() == 0) 255 return levels.get(0); 256 257 for (int su = 0; su < order; su++) { 258 Collections.sort(b.getAlignRes().get(su)); 259 msa.getEnsemble().getAtomArrays().add(atoms); 260 msa.getEnsemble().getStructureIdentifiers() 261 .add(result.getStructureId()); 262 } 263 264 result.setMultipleAlignment(msa); 265 result.setRefined(true); 266 result.setNumRepeats(order); 267 268 SymmetryAxes axes = recoverAxes(result); 269 result.setAxes(axes); 270 271 // Set the transformations and scores of the final alignment 272 SymmetryTools 273 .updateSymmetryTransformation(result.getAxes(), msa); 274 double tmScore = MultipleAlignmentScorer.getAvgTMScore(msa) 275 * msa.size(); 276 double rmsd = MultipleAlignmentScorer.getRMSD(msa); 277 msa.putScore(MultipleAlignmentScorer.AVGTM_SCORE, tmScore); 278 msa.putScore(MultipleAlignmentScorer.RMSD, rmsd); 279 280 return result; 281 } 282 283 /** 284 * The symmetry axes of each level are recovered after the symmetry analysis 285 * iterations have finished, using the stored MultipleAlignment at each 286 * symmetry level. 287 * @return SymmetryAxes 288 */ 289 private SymmetryAxes recoverAxes(CeSymmResult result) { 290 291 SymmetryAxes axes = new SymmetryAxes(); 292 293 for (int m = 0; m < levels.size(); m++) { 294 295 MultipleAlignment align = levels.get(m).getMultipleAlignment(); 296 Matrix4d axis = align.getBlockSet(0).getTransformations().get(1); 297 SymmetryType type = levels.get(m).getAxes().getElementaryAxis(0).getSymmType(); 298 int order = align.size(); 299 300 axes.addAxis(axis, order, type); 301 } 302 return axes; 303 } 304 305 /** 306 * Calculate the number of helix and strand SSE of a repeat. 307 * 308 * @param atoms 309 * Atom array of the repeat found 310 * @return int number of helix or strand SSE 311 */ 312 private static int countHelixStrandSSE(Atom[] atoms) { 313 314 List<SecStrucElement> sses = SecStrucTools 315 .getSecStrucElements(SymmetryTools.getGroups(atoms)); 316 int count = 0; 317 318 //keep track of different helix types 319 boolean helix = false; 320 int hEnd = 0; 321 322 for (SecStrucElement sse : sses) { 323 SecStrucType t = sse.getType(); 324 if (t.isBetaStrand()) { 325 helix = false; 326 count++; 327 } else if (t.isHelixType()){ 328 if (helix){ 329 // If this helix is contiguous to the previous 330 if (sse.getRange().getStart().getSeqNum() + 1 == hEnd) 331 hEnd = sse.getRange().getEnd().getSeqNum(); 332 else 333 count++; 334 } else 335 count++; 336 } else 337 helix = false; 338 } 339 return count; 340 } 341 342}