JAL-1622 fix bug computing superposition + associated refactorings
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
index deadbfc..cdd137b 100644 (file)
@@ -50,11 +50,10 @@ import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
-import jalview.structure.StructureMapping;
+import jalview.structure.AtomSpec;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
-import jalview.util.MessageManager;
 
 public abstract class JalviewJmolBinding extends AAStructureBindingModel
         implements JmolStatusListener, JmolSelectionListener,
@@ -243,47 +242,31 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     { hiddenCols });
   }
 
+  /**
+   * Construct and send a command to align structures against a reference
+   * structure, based on one or more sequence alignments
+   * 
+   * @param _alignment
+   *          an array of alignments to process
+   * @param _refStructure
+   *          an array of corresponding reference structures (index into pdb
+   *          file array); if a negative value is passed, the first PDB file
+   *          mapped to an alignment sequence is used as the reference for
+   *          superposition
+   * @param _hiddenCols
+   *          an array of corresponding hidden columns for each alignment
+   */
   public void superposeStructures(AlignmentI[] _alignment,
           int[] _refStructure, ColumnSelection[] _hiddenCols)
   {
-    assert (_alignment.length == _refStructure.length && _alignment.length != _hiddenCols.length);
-
     String[] files = getPdbFile();
-    // check to see if we are still waiting for Jmol files
-    long starttime = System.currentTimeMillis();
-    boolean waiting = true;
-    do
-    {
-      waiting = false;
-      for (String file : files)
-      {
-        try
-        {
-          // HACK - in Jalview 2.8 this call may not be threadsafe so we catch
-          // every possible exception
-          StructureMapping[] sm = getSsm().getMapping(file);
-          if (sm == null || sm.length == 0)
-          {
-            waiting = true;
-          }
-        } catch (Exception x)
-        {
-          waiting = true;
-        } catch (Error q)
-        {
-          waiting = true;
-        }
-      }
-      // we wait around for a reasonable time before we give up
-    } while (waiting
-            && System.currentTimeMillis() < (10000 + 1000 * files.length + starttime));
-    if (waiting)
+
+    if (!waitForFileLoad(files))
     {
-      System.err
-              .println("RUNTIME PROBLEM: Jmol seems to be taking a long time to process all the structures.");
       return;
     }
-    StringBuffer selectioncom = new StringBuffer();
+
+    StringBuilder selectioncom = new StringBuilder(256);
     // In principle - nSeconds specifies the speed of animation for each
     // superposition - but is seems to behave weirdly, so we don't specify it.
     String nSeconds = " ";
@@ -319,134 +302,78 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
                 + refStructure);
         refStructure = -1;
       }
-      if (refStructure < -1)
-      {
-        refStructure = -1;
-      }
-      StringBuffer command = new StringBuffer();
 
+      /*
+       * 'matched' array will hold 'true' for visible alignment columns where
+       * all sequences have a residue with a mapping to the PDB structure
+       */
       boolean matched[] = new boolean[alignment.getWidth()];
       for (int m = 0; m < matched.length; m++)
       {
-
         matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
       }
 
-      int commonrpositions[][] = new int[files.length][alignment.getWidth()];
-      String isel[] = new String[files.length];
-      // reference structure - all others are superposed in it
-      String[] targetC = new String[files.length];
-      String[] chainNames = new String[files.length];
-      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+      SuperposeData[] structures = new SuperposeData[files.length];
+      for (int f = 0; f < files.length; f++)
       {
-        StructureMapping[] mapping = getSsm().getMapping(files[pdbfnum]);
-        // RACE CONDITION - getMapping only returns Jmol loaded filenames once
-        // Jmol callback has completed.
-        if (mapping == null || mapping.length < 1)
-        {
-          throw new Error(MessageManager.getString("error.implementation_error_jmol_getting_data"));
-        }
-        int lastPos = -1;
-        final int sequenceCountForPdbFile = getSequence()[pdbfnum].length;
-        for (int s = 0; s < sequenceCountForPdbFile; s++)
-        {
-          for (int sp, m = 0; m < mapping.length; m++)
-          {
-            if (mapping[m].getSequence() == getSequence()[pdbfnum][s]
-                    && (sp = alignment.findIndex(getSequence()[pdbfnum][s])) > -1)
-            {
-              if (refStructure == -1)
-              {
-                refStructure = pdbfnum;
-              }
-              SequenceI asp = alignment.getSequenceAt(sp);
-              for (int r = 0; r < matched.length; r++)
-              {
-                if (!matched[r])
-                {
-                  continue;
-                }
-                matched[r] = false; // assume this is not a good site
-                if (r >= asp.getLength())
-                {
-                  continue;
-                }
-
-                if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
-                {
-                  // no mapping to gaps in sequence
-                  continue;
-                }
-                int t = asp.findPosition(r); // sequence position
-                int apos = mapping[m].getAtomNum(t);
-                int pos = mapping[m].getPDBResNum(t);
-
-                if (pos < 1 || pos == lastPos)
-                {
-                  // can't align unmapped sequence
-                  continue;
-                }
-                matched[r] = true; // this is a good ite
-                lastPos = pos;
-                // just record this residue position
-                commonrpositions[pdbfnum][r] = pos;
-              }
-              // create model selection suffix
-              isel[pdbfnum] = "/" + (pdbfnum + 1) + ".1";
-              if (mapping[m].getChain() == null
-                      || mapping[m].getChain().trim().length() == 0)
-              {
-                targetC[pdbfnum] = "";
-              }
-              else
-              {
-                targetC[pdbfnum] = ":" + mapping[m].getChain();
-              }
-              chainNames[pdbfnum] = mapping[m].getPdbId()
-                      + targetC[pdbfnum];
-              // move on to next pdb file
-              s = getSequence()[pdbfnum].length;
-              break;
-            }
-          }
-        }
+        structures[f] = new SuperposeData(alignment.getWidth());
       }
 
-      // TODO: consider bailing if nmatched less than 4 because superposition
-      // not
-      // well defined.
-      // TODO: refactor superposable position search (above) from jmol selection
-      // construction (below)
+      /*
+       * Calculate the superposable alignment columns ('matched'), and the
+       * corresponding structure residue positions (structures.pdbResNo)
+       */
+      int candidateRefStructure = findSuperposableResidues(alignment,
+              matched, structures);
+      if (refStructure < 0)
+      {
+        /*
+         * If no reference structure was specified, pick the first one that has
+         * a mapping in the alignment
+         */
+        refStructure = candidateRefStructure;
+      }
 
       String[] selcom = new String[files.length];
       int nmatched = 0;
-      // generate select statements to select regions to superimpose structures
+      for (boolean b : matched)
+      {
+        if (b)
+        {
+          nmatched++;
+        }
+      }
+      if (nmatched < 4)
+      {
+        // TODO: bail out here because superposition illdefined?
+      }
+
+      /*
+       * generate select statements to select regions to superimpose structures
+       */
       {
         for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
         {
-          String chainCd = targetC[pdbfnum];
+          String chainCd = ":" + structures[pdbfnum].chain;
           int lpos = -1;
           boolean run = false;
-          StringBuffer molsel = new StringBuffer();
+          StringBuilder molsel = new StringBuilder();
           molsel.append("{");
           for (int r = 0; r < matched.length; r++)
           {
             if (matched[r])
             {
-              if (pdbfnum == 0)
-              {
-                nmatched++;
-              }
-              if (lpos != commonrpositions[pdbfnum][r] - 1)
+              int pdbResNo = structures[pdbfnum].pdbResNo[r];
+              if (lpos != pdbResNo - 1)
               {
                 // discontinuity
                 if (lpos != -1)
                 {
                   molsel.append(lpos);
                   molsel.append(chainCd);
-                  // molsel.append("} {");
                   molsel.append("|");
                 }
+                run = false;
               }
               else
               {
@@ -459,11 +386,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
                 }
                 run = true;
               }
-              lpos = commonrpositions[pdbfnum][r];
-              // molsel.append(lpos);
+              lpos = pdbResNo;
             }
           }
-          // add final selection phrase
+          /*
+           * add final selection phrase
+           */
           if (lpos != -1)
           {
             molsel.append(lpos);
@@ -490,6 +418,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           }
         }
       }
+      StringBuilder command = new StringBuilder(256);
       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
       {
         if (pdbfnum == refStructure || selcom[pdbfnum] == null
@@ -499,23 +428,23 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         }
         command.append("echo ");
         command.append("\"Superposing (");
-        command.append(chainNames[pdbfnum]);
+        command.append(structures[pdbfnum].pdbId);
         command.append(") against reference (");
-        command.append(chainNames[refStructure]);
+        command.append(structures[refStructure].pdbId);
         command.append(")\";\ncompare " + nSeconds);
         command.append("{");
-        command.append(1 + pdbfnum);
+        command.append(Integer.toString(1 + pdbfnum));
         command.append(".1} {");
-        command.append(1 + refStructure);
+        command.append(Integer.toString(1 + refStructure));
         // conformation=1 excludes alternate locations for CA (JAL-1757)
         command.append(".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
 
-        // form the matched pair strings
-        String sep = "";
-        for (int s = 0; s < 2; s++)
-        {
-          command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
-        }
+        // for (int s = 0; s < 2; s++)
+        // {
+        // command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
+        // }
+        command.append(selcom[pdbfnum]);
+        command.append(selcom[refStructure]);
         command.append(" ROTATE TRANSLATE;\n");
       }
       if (selectioncom.length() > 0)
@@ -524,10 +453,11 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         evalStateCommand("select *; cartoons off; backbone; select ("
                 + selectioncom.toString() + "); cartoons; ");
         // selcom.append("; ribbons; ");
+        String cmdString = command.toString();
         System.out
-                .println("Superimpose command(s):\n" + command.toString());
+.println("Superimpose command(s):\n" + cmdString);
 
-        evalStateCommand(command.toString());
+        evalStateCommand(cmdString);
       }
     }
     if (selectioncom.length() > 0)
@@ -703,6 +633,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   // ////////////////////////////////
   // /StructureListener
+  @Override
   public synchronized String[] getPdbFile()
   {
     if (viewer == null)
@@ -786,6 +717,22 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     jmolpopup.show(x, y);
   }
 
+  /**
+   * Highlight zero, one or more atoms on the structure
+   */
+  @Override
+  public void highlightAtoms(List<AtomSpec> atoms)
+  {
+    if (atoms != null)
+    {
+      for (AtomSpec atom : atoms)
+      {
+        highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
+                atom.getChain(), atom.getPdbFile());
+      }
+    }
+  }
+
   // jmol/ssm only
   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
           String pdbfile)