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