JAL-2629 fix hmmbuild failing due to RF longer than alignment
[jalview.git] / src / jalview / io / HMMFile.java
1 package jalview.io;
2
3 import jalview.api.AlignExportSettingI;
4 import jalview.api.AlignmentViewPanel;
5 import jalview.datamodel.HMMNode;
6 import jalview.datamodel.HiddenMarkovModel;
7 import jalview.datamodel.SequenceI;
8
9 import java.io.BufferedReader;
10 import java.io.IOException;
11 import java.util.ArrayList;
12 import java.util.List;
13 import java.util.Scanner;
14
15
16 /**
17  * Adds capability to read in and write out HMMER3 files. .
18  * 
19  * 
20  * @author TZVanaalten
21  *
22  */
23 public class HMMFile extends AlignFile
24         implements AlignmentFileReaderI, AlignmentFileWriterI
25 {
26   // HMM to store file data
27   private HiddenMarkovModel hmm;
28
29   // number of possible transitions
30   private static final int NUMBER_OF_TRANSITIONS = 7;
31
32   private String NL = "\n";
33
34   //number of symbols in the alphabet used in the hidden Markov model
35   int numberOfSymbols;
36
37   private final String SPACE = " ";
38
39   private final String COMPO = "COMPO";
40
41   private final String EMPTY = "";
42
43   //This is a line that needs to be added to each HMMER� file. It is purely for readability.
44   private static final String TRANSITIONTYPELINE = "            m->m     m->i     m->d     i->m     i->i     d->m     d->d";
45
46   /**
47    * Parses immediately.
48    * 
49    * @param inFile
50    * @param type
51    * @throws IOException
52    */
53   public HMMFile(String inFile, DataSourceType type) throws IOException
54   {
55     super(inFile, type);
56   }
57
58   /**
59    * Parses immediately.
60    * 
61    * @param source
62    * @throws IOException
63    */
64   public HMMFile(FileParse source) throws IOException
65   {
66     super(source);
67   }
68
69   /**
70    * Default constructor, do not use!
71    */
72   public HMMFile()
73   {
74
75   }
76
77   /**
78    * Constructor for HMMFile used for exporting.
79    * 
80    * @param hmm
81    * @param exportImmediately
82    */
83   public HMMFile(HiddenMarkovModel markov)
84   {
85     hmm = markov;
86   }
87
88   /**
89    * For testing, do not use.
90    * 
91    * @param br
92    */
93   HMMFile(BufferedReader br)
94   {
95     dataIn = br;
96   }
97
98   /**
99    * Returns the HMM produced by reading in a HMMER3 file.
100    * 
101    * @return
102    */
103   public HiddenMarkovModel getHMM()
104   {
105     return hmm;
106   }
107
108   /**
109    * Sets the HMM used in this file.
110    * 
111    * @param model
112    */
113   public void setHMM(HiddenMarkovModel model)
114   {
115     this.hmm = model;
116   }
117
118   /**
119    * Gets the name of the hidden Markov model.
120    * 
121    * @return
122    */
123   public String getName()
124   {
125     return hmm.getName();
126   }
127
128   /**
129    * Reads the data from HMM file into the HMM field on this object.
130    * 
131    * @throws IOException
132    */
133   @Override
134   public void parse() throws IOException
135   {
136     try
137     {
138       hmm = new HiddenMarkovModel();
139       parseFileProperties(dataIn);
140       parseModel(dataIn);
141     } catch (Exception e)
142     {
143       e.printStackTrace();
144     }
145   }
146
147   /**
148    * Reads the data from HMM file into the HMM field on this object.
149    * 
150    * @throws IOException
151    */
152
153   public void parse(BufferedReader br) throws IOException
154   {
155     hmm = new HiddenMarkovModel();
156     parseFileProperties(br);
157     parseModel(br);
158   }
159
160
161
162   /**
163    * Imports the file properties from a HMMER3 file.
164    * 
165    * @param input
166    *          The buffered reader used to read in the file.
167    * @throws IOException
168    */
169   void parseFileProperties(BufferedReader input) throws IOException
170   {
171     boolean readingFile = true;
172     hmm.setFileHeader(input.readLine());
173     String line = input.readLine();
174     while (readingFile)
175     {
176       if (line != null)
177       {
178         Scanner parser = new Scanner(line);
179         String next = parser.next();
180         if ("HMM".equals(next)) // indicates start of HMM data (end of file
181                               // properties)
182         {
183           readingFile = false;
184           fillSymbols(parser);
185           numberOfSymbols = hmm.getNumberOfSymbols();
186         }
187         else if ("STATS".equals(next))
188         {
189           parser.next();
190           String key;
191           String value;
192           key = parser.next();
193           value = parser.next() + SPACE + SPACE + parser.next();
194           hmm.addFileProperty(key, value);
195         }
196         else
197         {
198           String key = next;
199           String value = parser.next();
200           while (parser.hasNext())
201           {
202             value = value + SPACE + parser.next();
203           }
204           hmm.addFileProperty(key, value);
205         }
206         parser.close();
207       }
208       line = input.readLine();
209       if (line == null)
210       {
211         readingFile = false;
212       }
213     }
214
215   }
216
217   /**
218    * Parses the model data from the HMMER3 file
219    * 
220    * @param input
221    *          The buffered reader used to read the file.
222    * @throws IOException
223    */
224   void parseModel(BufferedReader input) throws IOException
225   {
226     String line = input.readLine();
227     int node = 0;
228     while (!"//".equals(line))
229     {
230       hmm.getNodes().add(new HMMNode());
231       String next;
232       Scanner matchReader = new Scanner(line);
233       next = matchReader.next();
234       if (next.equals(COMPO) || node > 0)
235       {
236         // stores match emission line in list
237         List<Double> matches = new ArrayList<>();
238         matches = fillList(matchReader, numberOfSymbols);
239         hmm.getNodes().get(node).setMatchEmissions(matches);
240         if (node > 0)
241         {
242           parseAnnotations(matchReader, node);
243         }
244       }
245       matchReader.close();
246       // stores insert emission line in list
247       line = input.readLine();
248       Scanner insertReader = new Scanner(line);
249       List<Double> inserts = new ArrayList<>();
250       inserts = fillList(insertReader, numberOfSymbols);
251       hmm.getNodes().get(node).setInsertEmissions(inserts);
252       insertReader.close();
253
254       // stores state transition line in list
255       line = input.readLine();
256       Scanner transitionReader = new Scanner(line);
257       List<Double> transitions = new ArrayList<>();
258       transitions = fillList(transitionReader, NUMBER_OF_TRANSITIONS);
259       hmm.getNodes().get(node).setStateTransitions(transitions);
260       transitionReader.close();
261       line = input.readLine();
262       node++;
263     }
264
265   }
266
267   /**
268    * Parses the annotations on the match emission line.
269    * 
270    * @param scanner
271    *          The scanner which is processing match emission line.
272    * @param index
273    *          The index of node which is being scanned.
274    */
275   void parseAnnotations(Scanner scanner, int index)
276   {
277     if (hmm.mapIsActive() && scanner.hasNext())
278     {
279       int column;
280       column = scanner.nextInt();
281       hmm.getNodes().get(index).setAlignmentColumn(column - 1);
282       hmm.getNodeLookup().put(column - 1, index);
283     }
284     else
285     {
286       scanner.next();
287     }
288
289     if (scanner.hasNext())
290     {
291       char consensusR;
292       consensusR = charValue(scanner.next());
293       hmm.getNodes().get(index).setConsensusResidue(consensusR);
294     }
295
296     if (scanner.hasNext())
297     {
298       char reference;
299       reference = charValue(scanner.next());
300       hmm.getNodes().get(index).setReferenceAnnotation(reference);
301     }
302
303     if (scanner.hasNext())
304     {
305       char value;
306       value = charValue(scanner.next());
307       hmm.getNodes().get(index).setMaskValue(value);
308     }
309     if (scanner.hasNext())
310     {
311       char consensusS;
312       consensusS = charValue(scanner.next());
313       hmm.getNodes().get(index).setConsensusStructure(consensusS);
314     }
315   }
316
317
318
319   /**
320    * Fills a list of doubles based on an input line.
321    * 
322    * @param input
323    *          The scanner for the line containing the data to be transferred to
324    *          the list.
325    * @param numberOfElements
326    *          The number of elements in the list to be filled.
327    * @return filled list Returns the list of doubles.
328    * @throws IOException
329    */
330   static List<Double> fillList(Scanner input,
331           int numberOfElements) throws IOException
332   {
333     List<Double> list = new ArrayList<>();
334     for (int i = 0; i < numberOfElements; i++)
335     {
336
337       String next = input.next();
338       if (next.contains("*")) // state transitions to or from delete states
339                               // occasionally have values of -infinity. These
340                               // values are represented by an * in the .hmm
341                               // file.
342       {
343         list.add(Double.NEGATIVE_INFINITY);
344       }
345       else
346       {
347         double prob = Double.valueOf(next);
348         prob = Math.pow(Math.E, -prob);
349         list.add(prob);
350       }
351     }
352     if (list.size() < numberOfElements)
353     {
354       throw new IOException("Incomplete data");
355     }
356     return list;
357   }
358
359   /**
360    * Returns a string to be added to the StringBuilder containing the entire
361    * output String.
362    * 
363    * @param initialColumnSeparation
364    *          The initial whitespace separation between the left side of the
365    *          file and first character.
366    * @param columnSeparation
367    *          The separation between subsequent data entries.
368    * @param data
369    *          The list fo data to be added to the String.
370    * @return
371    */
372   String addData(int initialColumnSeparation,
373           int columnSeparation, List<String> data)
374   {
375     String line = EMPTY;
376     int index = 0;
377     for (String value : data)
378     {
379       if (index == 0)
380       {
381         line += String.format("%" + initialColumnSeparation + "s", value);
382       }
383       else
384       {
385         line += String.format("%" + columnSeparation + "s", value);
386       }
387       index++;
388     }
389     return line;
390   }
391
392   /**
393    * Converts list of characters into a list of Strings.
394    * 
395    * @param list
396    * @return Returns the list of Strings.
397    */
398   List<String> charListToStringList(List<Character> list)
399   {
400     List<String> strList = new ArrayList<>();
401     for (char value : list)
402     {
403       String strValue = Character.toString(value);
404       strList.add(strValue);
405     }
406     return strList;
407   }
408
409   /**
410    * Converts a list of doubles into a list of Strings, rounded to the nearest
411    * 5th decimal place.
412    * 
413    * @param list
414    * @param noOfDecimals
415    * @return
416    */
417   List<String> doubleListToStringList(List<Double> list)
418   {
419     List<String> strList = new ArrayList<>();
420     for (double value : list)
421     {
422       String strValue;
423       if (value > 0)
424       {
425         strValue = String.format("%.5f", value);
426
427       }
428       else if (value == -0.00000d)
429       {
430         strValue = "0.00000";
431       }
432       else
433       {
434         strValue = "*";
435       }
436
437       strList.add(strValue);
438     }
439     return strList;
440   }
441
442   /**
443    * Converts a primitive array of Strings to a list of Strings.
444    * 
445    * @param array
446    * @return
447    */
448   List<String> stringArrayToStringList(String[] array)
449   {
450     List<String> list = new ArrayList<>();
451     for (String value : array)
452     {
453       list.add(value);
454     }
455
456     return list;
457   }
458
459   /**
460    * Returns a string containing the model data.
461    */
462   String getModelAsString()
463   {
464     StringBuffer output = new StringBuffer();
465     String symbolLine = "HMM";
466     List<Character> charSymbols = hmm.getSymbols();
467     List<String> strSymbols;
468     strSymbols = charListToStringList(charSymbols);
469     symbolLine += addData(11, 9, strSymbols);
470     output.append(symbolLine);
471     output.append(NL + TRANSITIONTYPELINE);
472
473     int length = hmm.getLength();
474
475     for (int node = 0; node <= length; node++)
476     {
477       String matchLine;
478       if (node == 0)
479       {
480         matchLine = String.format("%7s", "COMPO");
481       }
482       else
483       {
484         matchLine = String.format("%7s", node);
485       }
486
487       List<String> strMatches;
488       List<Double> doubleMatches;
489       doubleMatches = convertListToLogSpace(
490               hmm.getNode(node).getMatchEmissions());
491       strMatches = doubleListToStringList(doubleMatches);
492       matchLine += addData(10, 9, strMatches);
493
494
495       if (node != 0)
496       {
497         matchLine += SPACE + (hmm.getNodeAlignmentColumn(node) + 1);
498         matchLine += SPACE + hmm.getConsensusResidue(node);
499         matchLine += SPACE + hmm.getReferenceAnnotation(node);
500         if (hmm.getFileHeader().contains("HMMER3/f"))
501         {
502           matchLine += SPACE + hmm.getMaskedValue(node);
503           matchLine += SPACE + hmm.getConsensusStructure(node);
504         }
505
506       }
507
508       output.append(NL + matchLine);
509       
510       String insertLine = EMPTY;
511       List<String> strInserts;
512       List<Double> doubleInserts;
513       doubleInserts = convertListToLogSpace(
514               hmm.getNode(node).getInsertEmissions());
515       strInserts = doubleListToStringList(doubleInserts);
516       insertLine += addData(17, 9, strInserts);
517
518       output.append(NL + insertLine);
519
520       String transitionLine = EMPTY;
521       List<String> strTransitions;
522       List<Double> doubleTransitions;
523       doubleTransitions = convertListToLogSpace(
524               hmm.getNode(node).getStateTransitions());
525       strTransitions = doubleListToStringList(doubleTransitions);
526       transitionLine += addData(17, 9, strTransitions);
527
528       output.append(NL + transitionLine);
529     }
530     return output.toString();
531   }
532
533   /**
534    * Returns a String containing the HMM file properties
535    */
536   String getFilePropertiesAsString()
537   {
538     StringBuffer output = new StringBuffer();
539     String line;
540
541     output.append(hmm.getFileHeader());
542     
543     line = String.format("%-5s %1s", "NAME", hmm.getName());
544     output.append(NL + line);
545
546     if (hmm.getAccessionNumber() != null)
547     {
548     line = String.format("%-5s %1s", "ACC", hmm.getAccessionNumber());
549       output.append(NL + line);
550     }
551
552     if (hmm.getDescription() != null)
553     {
554     line = String.format("%-5s %1s", "DESC", hmm.getDescription());
555       output.append(NL + line);
556     }
557     line = String.format("%-5s %1s", "LENG", hmm.getLength());
558     output.append(NL + line);
559
560     if (hmm.getMaxInstanceLength() != null)
561     {
562     line = String.format("%-5s %1s", "MAXL", hmm.getMaxInstanceLength());
563       output.append(NL + line);
564     }
565     line = String.format("%-5s %1s", "ALPH", hmm.getAlphabetType());
566     output.append(NL + line);
567
568     boolean status;
569     String statusStr;
570
571     status = hmm.referenceAnnotationIsActive();
572     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
573     line = String.format("%-5s %1s", "RF",
574             statusStr);
575     output.append(NL + line);
576
577     status = hmm.maskValueIsActive();
578     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
579     line = String.format("%-5s %1s", "MM",
580             statusStr);
581     output.append(NL + line);
582     
583     status = hmm.consensusResidueIsActive();
584     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
585     line = String.format("%-5s %1s", "CONS",
586             statusStr);
587     output.append(NL + line);
588
589     status = hmm.consensusStructureIsActive();
590     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
591     line = String.format("%-5s %1s", "CS",
592             statusStr);
593     output.append(NL + line);
594
595     status = hmm.mapIsActive();
596     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
597     line = String.format("%-5s %1s", "MAP",
598             statusStr);
599     output.append(NL + line);
600
601
602     if (hmm.getDate() != null)
603     {
604     line = String.format("%-5s %1s", "DATE", hmm.getDate());
605       output.append(NL + line);
606     }
607     if (hmm.getNumberOfSequences() != null)
608     {
609     line = String.format("%-5s %1s", "NSEQ", hmm.getNumberOfSequences());
610       output.append(NL + line);
611     }
612     if (hmm.getEffectiveNumberOfSequences() != null)
613     {
614     line = String.format("%-5s %1s", "EFFN",
615             hmm.getEffectiveNumberOfSequences());
616       output.append(NL + line);
617     }
618     if (hmm.getCheckSum() != null)
619     {
620     line = String.format("%-5s %1s", "CKSUM", hmm.getCheckSum());
621       output.append(NL + line);
622     }
623     if (hmm.getGatheringThreshold() != null)
624     {
625     line = String.format("%-5s %1s", "GA", hmm.getGatheringThreshold());
626       output.append(NL + line);
627     }
628
629     if (hmm.getTrustedCutoff() != null)
630     {
631     line = String.format("%-5s %1s", "TC", hmm.getTrustedCutoff());
632       output.append(NL + line);
633     }
634     if (hmm.getNoiseCutoff() != null)
635     {
636     line = String.format("%-5s %1s", "NC", hmm.getNoiseCutoff());
637       output.append(NL + line);
638     }
639     if (hmm.getMSV() != null)
640     {
641       line = String.format("%-19s %18s", "STATS LOCAL MSV", hmm.getMSV());
642       output.append(NL + line);
643
644       line = String.format("%-19s %18s", "STATS LOCAL VITERBI",
645               hmm.getViterbi());
646       output.append(NL + line);
647     
648       line = String.format("%-19s %18s", "STATS LOCAL FORWARD",
649               hmm.getForward());
650       output.append(NL + line);
651     }
652     return output.toString();
653   }
654
655
656   /**
657    * Returns the char value of a single lettered String.
658    * 
659    * @param string
660    * @return
661    */
662   char charValue(String string)
663   {
664     char character;
665     character = string.charAt(0);
666     return character;
667
668   }
669
670   @Override
671   public String print(SequenceI[] seqs, boolean jvsuffix)
672   {
673     if (seqs[0].getHMM() != null)
674     {
675       hmm = seqs[0].getHMM();
676     }
677     return print();
678   }
679
680   /**
681    * Prints the .hmm file to a String.
682    * 
683    * @return
684    */
685   public String print()
686   {
687     StringBuffer output = new StringBuffer();
688     output.append(getFilePropertiesAsString());
689     output.append(NL);
690     output.append(getModelAsString());
691     output.append(NL + "//");
692     return output.toString();
693   }
694
695   /**
696    * Converts the probabilities contained in a list into log space.
697    * 
698    * @param list
699    */
700   List<Double> convertListToLogSpace(List<Double> list)
701   {
702
703     List<Double> convertedList = new ArrayList<>();
704     for (int i = 0; i < list.size(); i++)
705     {
706       double prob = list.get(i);
707       double logProb = -1 * Math.log(prob);
708
709       convertedList.add(logProb);
710     }
711     return convertedList;
712
713
714   }
715
716   /**
717    * Returns the HMM sequence produced by reading a .hmm file.
718    */
719   @Override
720   public SequenceI[] getSeqsAsArray()
721   {
722     SequenceI hmmSeq = hmm.initHMMSequence();
723     SequenceI[] seq = new SequenceI[1];
724     seq[0] = hmmSeq;
725     return seq;
726
727   }
728
729   /**
730    * Fills symbol array and adds each symbol to an index lookup
731    * 
732    * @param parser
733    *          The scanner scanning the symbol line in the file.
734    */
735   public void fillSymbols(Scanner parser)
736   {
737     int i = 0;
738     while (parser.hasNext())
739     {
740       String strSymbol = parser.next();
741       char[] symbol = strSymbol.toCharArray();
742       hmm.getSymbols().add(symbol[0]);
743       hmm.setSymbolIndex(symbol[0], i);
744       i++;
745     }
746   }
747
748   @Override
749   public void setNewlineString(String newLine)
750   {
751     NL = newLine;
752   }
753
754   @Override
755   public void setExportSettings(AlignExportSettingI exportSettings)
756   {
757
758   }
759
760   @Override
761   public void configureForView(AlignmentViewPanel viewpanel)
762   {
763
764   }
765
766   @Override
767   public boolean hasWarningMessage()
768   {
769     return false;
770   }
771
772   @Override
773   public String getWarningMessage()
774   {
775     return "warning message";
776   }
777
778 }
779