2323package org .biojava .nbio .structure .symmetry .core ;
2424
2525import org .biojava .nbio .structure .Structure ;
26- import org .biojava .nbio .structure .cluster .Subunit ;
27- import org .biojava .nbio .structure .cluster .SubunitCluster ;
28- import org .biojava .nbio .structure .cluster .SubunitClusterer ;
29- import org .biojava .nbio .structure .cluster .SubunitClustererParameters ;
30- import org .biojava .nbio .structure .symmetry .utils .PowerSet ;
26+ import org .biojava .nbio .structure .cluster .*;
3127import org .slf4j .Logger ;
3228import org .slf4j .LoggerFactory ;
3329
30+ import org .jgrapht .alg .clique .CliqueMinimalSeparatorDecomposition ;
31+ import org .jgrapht .graph .DefaultEdge ;
32+ import org .jgrapht .graph .AsSubgraph ;
33+ import org .jgrapht .Graph ;
34+
3435import java .util .*;
36+ import java .util .stream .Collectors ;
3537
3638/**
3739 * Detects the symmetry (global, pseudo, internal and local) of protein
@@ -109,6 +111,7 @@ public static QuatSymmetryResults calcGlobalSymmetry(
109111 return calcQuatSymmetry (clusters , symmParams );
110112 }
111113
114+
112115 /**
113116 * Returns a List of LOCAL symmetry results. This means that a subset of the
114117 * {@link SubunitCluster} is left out of the symmetry calculation. Each
@@ -130,8 +133,8 @@ public static QuatSymmetryResults calcGlobalSymmetry(
130133 public static List <QuatSymmetryResults > calcLocalSymmetries (
131134 Structure structure , QuatSymmetryParameters symmParams ,
132135 SubunitClustererParameters clusterParams ) {
133- List < SubunitCluster > clusters = SubunitClusterer . cluster ( structure ,
134- clusterParams );
136+
137+ List < SubunitCluster > clusters = SubunitClusterer . cluster ( structure , clusterParams );
135138 return calcLocalSymmetries (clusters , symmParams );
136139 }
137140
@@ -156,8 +159,8 @@ public static List<QuatSymmetryResults> calcLocalSymmetries(
156159 public static List <QuatSymmetryResults > calcLocalSymmetries (
157160 List <Subunit > subunits , QuatSymmetryParameters symmParams ,
158161 SubunitClustererParameters clusterParams ) {
159- List < SubunitCluster > clusters = SubunitClusterer . cluster ( subunits ,
160- clusterParams );
162+
163+ List < SubunitCluster > clusters = SubunitClusterer . cluster ( subunits , clusterParams );
161164 return calcLocalSymmetries (clusters , symmParams );
162165 }
163166
@@ -177,79 +180,257 @@ public static List<QuatSymmetryResults> calcLocalSymmetries(
177180 * @return List of LOCAL quaternary structure symmetry results. Empty if
178181 * none.
179182 */
180- public static List <QuatSymmetryResults > calcLocalSymmetries (
181- List <SubunitCluster > clusters , QuatSymmetryParameters symmParams ) {
182183
183- List <QuatSymmetryResults > localSymmetries = new ArrayList < QuatSymmetryResults >();
184+ public static List <QuatSymmetryResults > calcLocalSymmetries ( List < SubunitCluster > clusters , QuatSymmetryParameters symmParams ) {
184185
185- // If it is homomeric return empty
186- if (clusters .size () < 2 )
187- return localSymmetries ;
186+ symmParams .setLocalTimeStart (System .nanoTime ());
188187
189- // If there are less than 3 or more than maximum Subunits return empty
190- QuatSymmetrySubunits subunits = new QuatSymmetrySubunits (clusters );
191- if (subunits .getSubunitCount () < 3
192- || subunits .getSubunitCount () > symmParams
193- .getMaximumLocalSubunits ())
194- return localSymmetries ;
188+ Set <Set <Integer >> knownResults = new HashSet <>();
189+ List <QuatSymmetryResults > outputSymmetries = new ArrayList <>();
190+
191+ List <SubunitCluster > nontrivialClusters =
192+ clusters .stream ().
193+ filter (cluster -> (cluster .size ()>1 )).
194+ collect (Collectors .toList ());
195+
196+ QuatSymmetrySubunits allSubunits = new QuatSymmetrySubunits (nontrivialClusters );
197+
198+ if (allSubunits .getSubunitCount () < 2 )
199+ return outputSymmetries ;
195200
196- // Start local symmetry calculations
197- long start = System .nanoTime ();
201+ Graph <Integer , DefaultEdge > graph = SubunitContactGraph .calculateGraph (allSubunits .getTraces ());
198202
199- // Calculate the power Set of the clusters
200- Set < Set < SubunitCluster >> powerSet = new PowerSet < SubunitCluster >()
201- . powerSet ( new LinkedHashSet < SubunitCluster >( clusters ) );
203+ List < Integer > allSubunitIds = new ArrayList <>( graph . vertexSet ());
204+ Collections . sort ( allSubunitIds );
205+ List < Integer > allSubunitClusterIds = allSubunits . getClusterIds ( );
202206
203- int combinations = 1 ;
204- for (Set <SubunitCluster > cluster : powerSet ) {
207+ Map <Integer , List <Integer >> clusterIdToSubunitIds =
208+ allSubunitIds .stream ().
209+ collect (Collectors .
210+ groupingBy (allSubunitClusterIds ::get , Collectors .toList ()));
205211
206- // Break if time limit passed
207- double time = (System .nanoTime () - start ) / 1000000000 ;
208- if (time > symmParams .getLocalTimeLimit ()) {
209- logger .warn ("Exceeded time limit for local symmetry "
210- + "calculations. {} seconds elapsed. "
211- + "Local symmetry results may be incomplete." , time );
212- break ;
212+ List <QuatSymmetryResults > redundantSymmetries = new ArrayList <>();
213+ if (clusters .size ()>1 ) {
214+ List <QuatSymmetryResults > clusterSymmetries =
215+ calcLocalSymmetriesCluster (nontrivialClusters , clusterIdToSubunitIds ,symmParams , knownResults );
216+ redundantSymmetries .addAll (clusterSymmetries );
217+ }
218+
219+ List <QuatSymmetryResults > graphSymmetries = calcLocalSymmetriesGraph (nontrivialClusters ,
220+ allSubunitClusterIds ,
221+ clusterIdToSubunitIds ,
222+ symmParams ,
223+ knownResults ,
224+ graph );
225+
226+ redundantSymmetries .addAll (graphSymmetries );
227+
228+ redundantSymmetries =
229+ redundantSymmetries .stream ().
230+ sorted (Comparator .
231+ comparing ((QuatSymmetryResults r ) -> r .getRotationGroup ().getOrder ()).
232+ thenComparing (r -> r .getSubunitCount ()).reversed ()).
233+ collect (Collectors .toList ());
234+
235+ for (QuatSymmetryResults redundantSymmetry :redundantSymmetries ) {
236+ if (outputSymmetries .stream ().
237+ noneMatch (redundantSymmetry ::isSupersededBy )) {
238+ outputSymmetries .add (redundantSymmetry );
213239 }
240+ }
241+
242+ double time = (System .nanoTime () - symmParams .getLocalTimeStart ()) / 1000000000 ;
243+ if (time > symmParams .getLocalTimeLimit ()) {
244+ logger .warn ("Exceeded time limit for local symmetry "
245+ + "calculations. {} seconds elapsed. "
246+ + "Local symmetry results may be incomplete." , time );
247+ }
248+ return outputSymmetries ;
249+ }
250+
251+ private static List <QuatSymmetryResults > calcLocalSymmetriesCluster (List <SubunitCluster > nontrivialClusters ,
252+ Map <Integer , List <Integer >> clusterIdToSubunitIds ,
253+ QuatSymmetryParameters symmParams ,
254+ Set <Set <Integer >> knownResults ) {
255+
256+ List <QuatSymmetryResults > clusterSymmetries = new ArrayList <>();
214257
215- // Break if maximum number of results exceeded
216- if (localSymmetries .size () > symmParams .getMaximumLocalResults ()) {
217- logger .warn ("Exceeded maximum number of local symmetry "
218- + "results. {} results calculated. "
219- + "Local symmetry results may be incomplete." ,
220- localSymmetries .size ());
221- break ;
258+ // find solutions for single clusters
259+ for (int i =0 ;i <nontrivialClusters .size ();i ++) {
260+ SubunitCluster nontrivialCluster = nontrivialClusters .get (i );
261+ QuatSymmetryResults localResult =
262+ calcQuatSymmetry (Collections .singletonList (nontrivialCluster ),symmParams );
263+
264+ if (localResult !=null && !localResult .getSymmetry ().equals ("C1" )) {
265+ localResult .setLocal (true );
266+ clusterSymmetries .add (localResult );
267+ Set <Integer > knownResult = new HashSet <>(clusterIdToSubunitIds .get (i ));
268+ knownResults .add (knownResult );
222269 }
270+ }
271+
272+ // group clusters by symmetries found, in case they all share axes and have the same number of subunits
273+ Map <String , Map <Integer ,List <QuatSymmetryResults >>> groupedSymmetries =
274+ clusterSymmetries .stream ().
275+ collect (Collectors .
276+ groupingBy (QuatSymmetryResults ::getSymmetry ,Collectors .
277+ groupingBy (QuatSymmetryResults ::getSubunitCount ,Collectors .toList ())));
278+
279+ for (Map <Integer ,List <QuatSymmetryResults >> symmetriesByGroup : groupedSymmetries .values ()) {
280+ for (List <QuatSymmetryResults > symmetriesBySubunits : symmetriesByGroup .values ()) {
281+
282+ List <SubunitCluster > clustersGroup =
283+ symmetriesBySubunits .stream ().
284+ map (r -> r .getSubunitClusters ().get (0 )).
285+ collect (Collectors .toList ());
286+
287+ if (clustersGroup .size () < 2 ) {
288+ continue ;
289+ }
223290
224- // Break if maximum number of tried combinations exceeded
225- if (combinations > symmParams .getMaximumLocalCombinations ()) {
226- logger .warn ("Exceeded maximum number of local combinations."
227- + " {} combinations tried. "
228- + "Local symmetry results may be incomplete." ,
229- combinations );
230- break ;
291+ QuatSymmetryResults localResult = calcQuatSymmetry (clustersGroup ,symmParams );
292+
293+ if (localResult !=null && !localResult .getSymmetry ().equals ("C1" )) {
294+ localResult .setLocal (true );
295+ clusterSymmetries .add (localResult );
296+ // find subunit ids in this cluster list
297+ Set <Integer > knownResult = new HashSet <>();
298+ for (SubunitCluster cluster : clustersGroup ) {
299+ int i = nontrivialClusters .indexOf (cluster );
300+ knownResult .addAll (clusterIdToSubunitIds .get (i ));
301+ }
302+ knownResults .add (knownResult );
303+ }
231304 }
305+ }
306+ return clusterSymmetries ;
307+ }
308+
309+
310+ private static List <QuatSymmetryResults > calcLocalSymmetriesGraph (final List <SubunitCluster > allClusters ,
311+ final List <Integer > allSubunitClusterIds ,
312+ final Map <Integer , List <Integer >> clusterIdToSubunitIds ,
313+ QuatSymmetryParameters symmParams ,
314+ Set <Set <Integer >> knownResults ,
315+ Graph <Integer , DefaultEdge > graph ) {
316+
317+ List <QuatSymmetryResults > localSymmetries = new ArrayList <>();
318+
319+ double time = (System .nanoTime () - symmParams .getLocalTimeStart ()) / 1000000000 ;
320+ if (time > symmParams .getLocalTimeLimit ()) {
321+ return localSymmetries ;
322+ }
323+
324+ // calc components of a (sub-)graph
325+ CliqueMinimalSeparatorDecomposition <Integer , DefaultEdge > cmsd =
326+ new CliqueMinimalSeparatorDecomposition <>(graph );
327+
328+ Set <Set <Integer >> graphComponents =
329+ cmsd .getAtoms ().stream ().
330+ filter (component -> component .size ()>1 ).
331+ collect (Collectors .toSet ());
332+
333+ //subtract known results
334+ graphComponents .removeAll (knownResults );
232335
233- // Do not use empty set or identity set
234- if (cluster .size () == 0 || cluster .size () == clusters .size ())
336+ for (Set <Integer > graphComponent : graphComponents ) {
337+ knownResults .add (graphComponent );
338+
339+ List <Integer > usedSubunitIds = new ArrayList <>(graphComponent );
340+ Collections .sort (usedSubunitIds );
341+ List <SubunitCluster > localClusters =
342+ trimSubunitClusters (allClusters , allSubunitClusterIds , clusterIdToSubunitIds , usedSubunitIds );
343+
344+ if (localClusters .size ()==0 ) {
235345 continue ;
346+ }
236347
237- List <SubunitCluster > localClusters = new ArrayList <SubunitCluster >(
238- cluster );
239- QuatSymmetryResults localResult = calcGlobalSymmetry (localClusters ,
240- symmParams );
348+ //NB: usedSubunitIds might have changed when trimming clusters
349+ Set <Integer > usedSubunitIdsSet = new HashSet <>(usedSubunitIds );
350+ if (!graphComponent .equals (usedSubunitIdsSet )) {
351+ if (knownResults .contains (usedSubunitIdsSet )) {
352+ continue ;
353+ } else {
354+ knownResults .add (usedSubunitIdsSet );
355+ }
356+ }
241357
242- if (!localResult .getSymmetry ().equals ("C1" )) {
358+ QuatSymmetryResults localResult = calcQuatSymmetry (localClusters ,symmParams );
359+ if (localResult !=null && !localResult .getSymmetry ().equals ("C1" )) {
243360 localResult .setLocal (true );
244361 localSymmetries .add (localResult );
362+ continue ;
363+ }
364+
365+ if (usedSubunitIds .size () < 3 ) {
366+ continue ;
367+ }
368+
369+ for (Integer removeSubunitId : usedSubunitIds ) {
370+
371+ Set <Integer > prunedGraphVertices = new HashSet <>(usedSubunitIds );
372+ prunedGraphVertices .remove (removeSubunitId );
373+ if (knownResults .contains (prunedGraphVertices )) {
374+ continue ;
375+ }
376+ knownResults .add (prunedGraphVertices );
377+
378+ Graph <Integer , DefaultEdge > subGraph = new AsSubgraph <>(graph ,prunedGraphVertices );
379+
380+ List <QuatSymmetryResults > localSubSymmetries = calcLocalSymmetriesGraph (allClusters ,
381+ allSubunitClusterIds ,
382+ clusterIdToSubunitIds ,
383+ symmParams ,
384+ knownResults ,
385+ subGraph );
386+ localSymmetries .addAll (localSubSymmetries );
245387 }
246388
247- combinations ++;
248389 }
249390
250391 return localSymmetries ;
251392 }
252393
394+ private static List <SubunitCluster > trimSubunitClusters (List <SubunitCluster > allClusters ,
395+ List <Integer > allSubunitClusterIds ,
396+ Map <Integer , List <Integer >> clusterIdToSubunitIds ,
397+ List <Integer > usedSubunitIds ) {
398+ List <SubunitCluster > localClusters = new ArrayList <>();
399+
400+ Set <Integer > usedClusterIds =
401+ usedSubunitIds .stream ().
402+ map (allSubunitClusterIds ::get ).
403+ distinct ().
404+ collect (Collectors .toSet ());
405+
406+ // for each used cluster, remove unused subunits
407+ for (Integer usedClusterId :usedClusterIds ) {
408+ SubunitCluster originalCluster = allClusters .get (usedClusterId );
409+ List <Integer > allSubunitIdsInCluster = clusterIdToSubunitIds .get (usedClusterId );
410+
411+ //subunit numbering is global for the entire graph
412+ // make it zero-based for the inside of a cluster
413+ int minSUValue = Collections .min (allSubunitIdsInCluster );
414+ List <Integer > usedSubunitIdsInCluster = new ArrayList <>(allSubunitIdsInCluster );
415+ usedSubunitIdsInCluster .retainAll (usedSubunitIds );
416+
417+ List <Integer > subunitsToRetain =
418+ usedSubunitIdsInCluster .stream ().
419+ map (i -> i -minSUValue ).
420+ collect (Collectors .toList ());
421+
422+ if (subunitsToRetain .size ()>1 ) {
423+ SubunitCluster filteredCluster = new SubunitCluster (originalCluster , subunitsToRetain );
424+ localClusters .add (filteredCluster );
425+ } else {
426+ // if the cluster ends up having only 1 subunit, remove it from further processing
427+ usedSubunitIds .removeAll (usedSubunitIdsInCluster );
428+ }
429+ }
430+ return localClusters ;
431+ }
432+
433+
253434 private static QuatSymmetryResults calcQuatSymmetry (
254435 List <SubunitCluster > clusters , QuatSymmetryParameters parameters ) {
255436
0 commit comments