JAL-2629 add documentation to previous work
[jalview.git] / src / jalview / datamodel / HiddenMarkovModel.java
1 package jalview.datamodel;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Scanner;
8
9 /**
10  * Data structure which stores a hidden Markov model. Currently contains file
11  * properties as well, not sure whether these should be transferred to the
12  * HMMFile class
13  * 
14  * @author TZVanaalten
15  * 
16  */
17 public class HiddenMarkovModel
18 {
19
20
21   // Stores file properties. Do not directly access this field as it contains
22   // only string value - use the getter methods. For example, to find the length
23   // of theHMM, use getModelLength()to return an int value
24   Map<String, String> fileProperties = new HashMap<>();
25   
26   // contains all of the symbols used in this model. The index of each symbol
27   // represents its lookup value
28   List<Character> symbols = new ArrayList<>();
29
30   // contains information for each node in the model. The begin node is at index
31   // 0. Node 0 contains average emission probabilities for each symbol
32   List<HMMNode> nodes = new ArrayList<>();
33
34   // contains the HMM node for each alignment column, alignment columns start at
35   // index 0;
36   Map<Integer, Integer> nodeLookup = new HashMap<>();
37   
38   // contains the symbol index for each symbol
39   Map<Character, Integer> symbolIndexLookup = new HashMap<>();
40
41   final static String YES = "yes";
42
43   final static String NO = "no";
44
45   int numberOfSymbols;
46   
47   // keys for file properties hashmap
48   private final String NAME = "NAME";
49
50   private final String ACCESSION_NUMBER = "ACC";
51
52   private final String DESCRIPTION = "DESC";
53
54   private final String LENGTH = "LENG";
55
56   private final String MAX_LENGTH = "MAXL";
57
58   private final String ALPHABET = "ALPH";
59
60   private final String DATE = "DATE";
61
62   private final String COMMAND_LOG = "COM";
63
64   private final String NUMBER_OF_SEQUENCES = "NSEQ";
65
66   private final String EFF_NUMBER_OF_SEQUENCES = "EFFN";
67
68   private final String CHECK_SUM = "CKSUM";
69
70   private final String GATHERING_THRESHOLDS = "GA";
71
72   private final String TRUSTED_CUTOFFS = "TC";
73
74   private final String NOISE_CUTOFFS = "NC";
75
76   private final String STATISTICS = "STATS";
77
78   private final String COMPO = "COMPO";
79   
80   private final String GATHERING_THRESHOLD = "GA";
81
82   private final String TRUSTED_CUTOFF = "TC";
83
84   private final String NOISE_CUTOFF = "NC";
85
86   private final String VITERBI = "VITERBI";
87
88   private final String MSV = "MSV";
89
90   private final String FORWARD = "FORWARD";
91
92   private final String MAP = "MAP";
93
94   private final String REFERENCE_ANNOTATION = "RF";
95
96   private final String CONSENSUS_RESIDUE = "CONS";
97
98   private final String CONSENSUS_STRUCTURE = "CS";
99
100   private final String MASKED_VALUE = "MM";
101   
102   public static final int MATCHTOMATCH = 0;
103
104   public static final int MATCHTOINSERT = 1;
105
106   public static final int MATCHTODELETE = 2;
107
108   public static final int INSERTTOMATCH = 3;
109
110   public static final int INSERTTOINSERT = 4;
111
112   public static final int DELETETOMATCH = 5;
113
114   public static final int DELETETODELETE = 6;
115
116   String fileHeader;
117
118   public HiddenMarkovModel()
119   {
120
121   }
122
123   public HiddenMarkovModel(HiddenMarkovModel hmm)
124   {
125     super();
126     this.fileProperties = new HashMap<>(hmm.fileProperties);
127     this.symbols = new ArrayList<>(hmm.symbols);
128     this.nodes = new ArrayList<>(hmm.nodes);
129     this.nodeLookup = new HashMap<>(hmm.nodeLookup);
130     this.symbolIndexLookup = new HashMap<>(
131             hmm.symbolIndexLookup);
132     this.numberOfSymbols = hmm.numberOfSymbols;
133     this.fileHeader = new String(hmm.fileHeader);
134   }
135
136   /**
137    * Gets the file header of the .hmm file this model came from.
138    * 
139    * @return
140    */
141   public String getFileHeader()
142   {
143     return fileHeader;
144   }
145
146   /**
147    * Sets the file header of this model.
148    * 
149    * @param header
150    */
151   public void setFileHeader(String header)
152   {
153     fileHeader = header;
154   }
155
156   /**
157    * Returns the map containing the matches between nodes and alignment column
158    * indexes.
159    * 
160    * @return
161    * 
162    */
163   public Map<Integer, Integer> getNodeLookup()
164   {
165     return nodeLookup;
166   }
167
168   /**
169    * Returns the list of symbols used in this hidden Markov model.
170    * 
171    * @return
172    */
173   public List<Character> getSymbols()
174   {
175     return symbols;
176   }
177   
178   /**
179    * Returns the file properties.
180    * 
181    * @return
182    */
183   public Map<String, String> getFileProperties()
184   {
185     return fileProperties;
186   }
187
188   /**
189    * Gets the node in the hidden Markov model at the specified position.
190    * 
191    * @param nodeIndex
192    *          The index of the node requested. Node 0 optionally contains the
193    *          average match emission probabilities across the entire model, and
194    *          always contains the insert emission probabilities and state
195    *          transition probabilities for the begin node. Node 1 contains the
196    *          first node in the HMM that can correspond to a column in the
197    *          alignment.
198    * @return
199    */
200   public HMMNode getNode(int nodeIndex)
201   {
202     return getNodes().get(nodeIndex);
203   }
204
205   /**
206    * Sets the list of symbols used in the hidden Markov model to the list
207    * specified.
208    * 
209    * @param symbolsL
210    *          The list of symbols to which the current list is to be changed.
211    * 
212    */
213   public void setSymbols(List<Character> symbolsL)
214   {
215     this.symbols = symbolsL;
216   }
217
218   /**
219    * Returns the name of the sequence alignment on which the HMM is based.
220    * 
221    * @return
222    */
223   public String getName()
224   {
225     return fileProperties.get(NAME);
226   }
227   
228   /**
229    * Returns the accession number.
230    * @return
231    */
232   public String getAccessionNumber()
233   {
234     return fileProperties.get(ACCESSION_NUMBER);
235   }
236
237   /**
238    * Returns a description of the sequence alignment on which the hidden Markov
239    * model is based.
240    * 
241    * @return
242    */
243   public String getDescription()
244   {
245     return fileProperties.get(DESCRIPTION);
246   }
247
248   /**
249    * Returns the length of the hidden Markov model.
250    * 
251    * @return
252    */
253   public Integer getLength()
254   {
255     if (fileProperties.get(LENGTH) == null)
256     {
257       return null;
258     }
259     return Integer.parseInt(fileProperties.get(LENGTH));
260   }
261
262   /**
263    * Returns the max instance length within the hidden Markov model.
264    * 
265    * @return
266    */
267   public Integer getMaxInstanceLength()
268   {
269     if (fileProperties.get(MAX_LENGTH) == null)
270     {
271       return null;
272     }
273     return Integer.parseInt(fileProperties.get(MAX_LENGTH));
274   }
275
276   /**
277    * Returns the type of symbol alphabet - "amino", "DNA", "RNA" are the
278    * options. Other alphabets may be added.
279    * 
280    * @return
281    */
282   public String getAlphabetType()
283   {
284     return fileProperties.get(ALPHABET);
285   }
286
287   /**
288    * Returns the date as a String.
289    * 
290    * @return
291    */
292   public String getDate()
293   {
294     return fileProperties.get(DATE);
295   }
296
297   /**
298    * Returns the command line log.
299    * 
300    * @return
301    */
302   public String getCommandLineLog()
303   {
304     return fileProperties.get(COMMAND_LOG);
305   }
306
307   /**
308    * Returns the number of sequences on which the HMM was trained.
309    * 
310    * @return
311    */
312   public Integer getNumberOfSequences()
313   {
314     if (fileProperties.get(NUMBER_OF_SEQUENCES) == null)
315     {
316       return null;
317     }
318     return Integer.parseInt(fileProperties.get(NUMBER_OF_SEQUENCES));
319   }
320
321   /**
322    * Returns the effective number of sequences on which the HMM was based.
323    * 
324    * @param value
325    */
326   public Double getEffectiveNumberOfSequences()
327   {
328     if (fileProperties.get(LENGTH) == null)
329     {
330       return null;
331     }
332     return Double.parseDouble(fileProperties.get(EFF_NUMBER_OF_SEQUENCES));
333   }
334
335   /**
336    * Returns the checksum.
337    * 
338    * @return
339    */
340   public Long getCheckSum()
341   {
342     if (fileProperties.get(LENGTH) == null)
343     {
344       return null;
345     }
346     return Long.parseLong(fileProperties.get(CHECK_SUM));
347   }
348
349   /**
350    * Returns the list of nodes in this HMM.
351    * 
352    * @return
353    */
354   public List<HMMNode> getNodes()
355   {
356     return nodes;
357   }
358
359   /**
360    * Sets the list of nodes in this HMM to the given list.
361    * 
362    * @param nodes
363    *          The list of nodes to which the current list of nodes is being
364    *          changed.
365    */
366   public void setNodes(List<HMMNode> nodes)
367   {
368     this.nodes = nodes;
369   }
370   
371   /**
372    * Gets the match emission probability for a given symbol at a column in the
373    * alignment.
374    * 
375    * @param alignColumn
376    *          The index of the alignment column, starting at index 0. Index 0
377    *          usually corresponds to index 1 in the HMM.
378    * @param symbol
379    *          The symbol for which the desired probability is being requested.
380    * @return
381    * 
382    */
383   public Double getMatchEmissionProbability(int alignColumn, char symbol)
384   {
385     int symbolIndex;
386     int nodeIndex;
387     Double probability;
388     if (!symbolIndexLookup.containsKey(symbol))
389     {
390       return 0d;
391     }
392     symbolIndex = symbolIndexLookup.get(symbol);
393     if (nodeLookup.containsKey(alignColumn))
394     {
395       nodeIndex = nodeLookup.get(alignColumn);
396       probability = getNode(nodeIndex).getMatchEmissions().get(symbolIndex);
397       return probability;
398     }
399     else
400     {
401       return 0d;
402     }
403
404   }
405
406   /**
407    * Gets the insert emission probability for a given symbol at a column in the
408    * alignment.
409    * 
410    * @param alignColumn
411    *          The index of the alignment column, starting at index 0. Index 0
412    *          usually corresponds to index 1 in the HMM.
413    * @param symbol
414    *          The symbol for which the desired probability is being requested.
415    * @return
416    * 
417    */
418   public Double getInsertEmissionProbability(int alignColumn, char symbol)
419   {
420     int symbolIndex;
421     int nodeIndex;
422     Double probability;
423     if (!symbolIndexLookup.containsKey(symbol))
424     {
425       return 0d;
426     }
427     symbolIndex = symbolIndexLookup.get(symbol);
428     if (nodeLookup.containsKey(alignColumn))
429     {
430       nodeIndex = nodeLookup.get(alignColumn);
431       probability = getNode(nodeIndex).getInsertEmissions()
432               .get(symbolIndex);
433       return probability;
434     }
435     else
436     {
437       return 0d;
438     }
439
440   }
441   
442   /**
443    * Gets the state transition probability for a given symbol at a column in the
444    * alignment.
445    * 
446    * @param alignColumn
447    *          The index of the alignment column, starting at index 0. Index 0
448    *          usually corresponds to index 1 in the HMM.
449    * @param symbol
450    *          The symbol for which the desired probability is being requested.
451    * @return
452    * 
453    */
454   public Double getStateTransitionProbability(int alignColumn,
455           int transition)
456   {
457     int transitionIndex;
458     int nodeIndex;
459     Double probability;
460     if (nodeLookup.containsKey(alignColumn))
461     {
462       nodeIndex = nodeLookup.get(alignColumn);
463       probability = getNode(nodeIndex).getStateTransitions()
464               .get(transition);
465       return probability;
466     }
467     else
468     {
469       return 0d;
470     }
471
472   }
473   
474   /**
475    * Returns the alignment column linked to the node at the given index.
476    * 
477    * @param nodeIndex
478    *          The index of the node, starting from index 1. Index 0 is the begin
479    *          node, which does not correspond to a column in the alignment.
480    * @return
481    */
482   public Integer getNodeAlignmentColumn(int nodeIndex)
483   {
484     Integer value = nodes.get(nodeIndex).getAlignmentColumn();
485     return value;
486   }
487   
488   /**
489    * Returns the consensus residue at the specified node.
490    * 
491    * @param nodeIndex
492    *          The index of the specified node.
493    * @return
494    */
495   public char getConsensusResidue(int nodeIndex)
496   {
497    char value = nodes.get(nodeIndex).getConsensusResidue();
498    return value;
499   }
500   
501   /**
502    * Returns the consensus at a given alignment column.
503    * 
504    * @param columnIndex
505    *          The index of the column in the alignment for which the consensus
506    *          is desired. The list of columns starts at index 0.
507    * @return
508    */
509   public char getConsensusAtAlignColumn(int columnIndex)
510   {
511     char mostLikely = '-';
512     if (consensusResidueIsActive())
513     {
514
515     Integer index = findNodeIndex(columnIndex);
516     if (index == null)
517     {
518       return '-';
519     }
520       mostLikely = getNodes().get(index).getConsensusResidue();
521       return mostLikely;
522     }
523     else
524     {
525       double highestProb = 0;
526       for (char character : symbols)
527       {
528         Double prob = getMatchEmissionProbability(columnIndex, character);
529         if (prob > highestProb)
530         {
531           highestProb = prob;
532           mostLikely = character;
533         }
534       }
535       return mostLikely;
536     }
537
538   }
539
540   /**
541    * Returns the reference annotation at the specified node.
542    * 
543    * @param nodeIndex
544    *          The index of the specified node.
545    * @return
546    */
547   public char getReferenceAnnotation(int nodeIndex)
548   {
549    char value = nodes.get(nodeIndex).getReferenceAnnotation();
550    return value;
551   }
552   
553   /**
554    * Returns the mask value at the specified node.
555    * 
556    * @param nodeIndex
557    *          The index of the specified node.
558    * @return
559    */
560   public char getMaskedValue(int nodeIndex)
561   {
562    char value = nodes.get(nodeIndex).getMaskValue();
563    return value;
564   }
565   
566   /**
567    * Returns the consensus structure at the specified node.
568    * 
569    * @param nodeIndex
570    *          The index of the specified node.
571    * @return
572    */
573   public char getConsensusStructure(int nodeIndex)
574   {
575    char value = nodes.get(nodeIndex).getConsensusStructure();
576    return value;
577   }
578   
579   /**
580    * Returns the average match emission probability for a given symbol
581    * 
582    * @param symbolIndex
583    *          The index of the symbol.
584    * @return
585    * 
586    */
587   public double getAverageMatchEmission(int symbolIndex)
588   {
589     double value = nodes.get(0).getMatchEmissions().get(symbolIndex);
590     return value;
591   }
592
593   /**
594    * Returns the number of symbols in the alphabet used in this HMM.
595    * 
596    * @return
597    */
598   public int getNumberOfSymbols()
599   {
600     return numberOfSymbols;
601   }
602
603   /**
604    * Fills symbol array and whilst doing so, updates the value of the number of
605    * symbols.
606    * 
607    * @param parser
608    *          The scanner scanning the symbol line in the file.
609    */
610   public void fillSymbols(Scanner parser)
611   {
612     int i = 0;
613     while (parser.hasNext())
614     {
615       String strSymbol = parser.next();
616       char[] symbol = strSymbol.toCharArray();
617       symbols.add(symbol[0]);
618       symbolIndexLookup.put(symbol[0], i);
619       i++;
620     }
621     numberOfSymbols = symbols.size();
622   }
623
624   /**
625    * Adds a file property.
626    * 
627    * @param key
628    * @param value
629    */
630   public void addFileProperty(String key, String value)
631   {
632     fileProperties.put(key, value);
633   }
634
635   /**
636    * Returns a boolean indicating whether the reference annotation is active.
637    * 
638    * @return
639    */
640   public boolean referenceAnnotationIsActive()
641   {
642     String status;
643     status = fileProperties.get(REFERENCE_ANNOTATION);
644     if (status == null)
645     {
646       return false;
647     }
648     switch (status)
649     {
650     case YES:
651       return true;
652     case NO:
653       return false;
654     default:
655       return false;
656     }
657
658   }
659
660   /**
661    * Returns a boolean indicating whether the mask value annotation is active.
662    * 
663    * @return
664    */
665   public boolean maskValueIsActive()
666   {
667     String status;
668     status = fileProperties.get(MASKED_VALUE);
669     if (status == null)
670     {
671       return false;
672     }
673     switch (status)
674     {
675     case YES:
676       return true;
677     case NO:
678       return false;
679     default:
680       return false;
681     }
682
683   }
684
685   /**
686    * Returns a boolean indicating whether the consensus residue annotation is
687    * active.
688    * 
689    * @return
690    */
691   public boolean consensusResidueIsActive()
692   {
693     String status;
694     status = fileProperties.get(CONSENSUS_RESIDUE);
695     if (status == null)
696     {
697       return false;
698     }
699     switch (status)
700     {
701     case YES:
702       return true;
703     case NO:
704       return false;
705     default:
706       return false;
707     }
708
709   }
710
711   /**
712    * Returns a boolean indicating whether the consensus structure annotation is
713    * active.
714    * 
715    * @return
716    */
717   public boolean consensusStructureIsActive()
718   {
719     String status;
720     status = fileProperties.get(CONSENSUS_STRUCTURE);
721     if (status == null)
722     {
723       return false;
724     }
725     switch (status)
726     {
727     case YES:
728       return true;
729     case NO:
730       return false;
731     default:
732       return false;
733     }
734
735   }
736
737   /**
738    * Returns a boolean indicating whether the MAP annotation is active.
739    * 
740    * @return
741    */
742   public boolean mapIsActive()
743   {
744     String status;
745     status = fileProperties.get(MAP);
746     if (status == null)
747     {
748       return false;
749     }
750     switch (status)
751     {
752     case YES:
753       return true;
754     case NO:
755       return false;
756     default:
757       return false;
758     }
759
760   }
761
762   /**
763    * Sets the alignment column of the specified node.
764    * 
765    * @param nodeIndex
766    * 
767    * @param column
768    * 
769    */
770   public void setAlignmentColumn(int nodeIndex, int column)
771   {
772     nodes.get(nodeIndex).setAlignmentColumn(column);
773   }
774
775   /**
776    * Sets the reference annotation at a given node.
777    * 
778    * @param nodeIndex
779    * @param value
780    */
781   public void setReferenceAnnotation(int nodeIndex, char value)
782   {
783     nodes.get(nodeIndex).setReferenceAnnotation(value);
784   }
785
786   /**
787    * Sets the consensus residue at a given node.
788    * 
789    * @param nodeIndex
790    * @param value
791    */
792   public void setConsensusResidue(int nodeIndex, char value)
793   {
794     nodes.get(nodeIndex).setConsensusResidue(value);
795   }
796
797   /**
798    * Sets the consensus structure at a given node.
799    * 
800    * @param nodeIndex
801    * @param value
802    */
803   public void setConsensusStructure(int nodeIndex, char value)
804   {
805     nodes.get(nodeIndex).setConsensusStructure(value);
806   }
807
808   /**
809    * Sets the mask value at a given node.
810    * 
811    * @param nodeIndex
812    * @param value
813    */
814   public void setMaskValue(int nodeIndex, char value)
815   {
816     nodes.get(nodeIndex).setMaskValue(value);
817   }
818
819   /**
820    * Temporary implementation, should not be used.
821    * 
822    * @return
823    */
824   public String getGatheringThreshold()
825   {
826     String value;
827     value = fileProperties.get("GA");
828     return value;
829   }
830
831   /**
832    * Temporary implementation, should not be used.
833    * 
834    * @return
835    */
836   public String getNoiseCutoff()
837   {
838     String value;
839     value = fileProperties.get("NC");
840     return value;
841   }
842
843   /**
844    * Temporary implementation, should not be used.
845    * 
846    * @return
847    */
848   public String getTrustedCutoff()
849   {
850     String value;
851     value = fileProperties.get("TC");
852     return value;
853   }
854
855   /**
856    * Temporary implementation, should not be used.
857    * 
858    * @return
859    */
860   public String getViterbi()
861   {
862     String value;
863     value = fileProperties.get(VITERBI);
864     return value;
865   }
866
867   /**
868    * Temporary implementation, should not be used.
869    * 
870    * @return
871    */
872   public String getMSV()
873   {
874     String value;
875     value = fileProperties.get(MSV);
876     return value;
877   }
878
879   /**
880    * Temporary implementation, should not be used.
881    * 
882    * @return
883    */
884   public String getForward()
885   {
886     String value;
887     value = fileProperties.get(FORWARD);
888     return value;
889   }
890
891   /**
892    * Sets the activation status of the MAP annotation.
893    * 
894    * @param status
895    */
896   public void setMAPStatus(boolean status)
897   {
898     fileProperties.put(MAP, status ? YES : NO);
899   }
900
901   /**
902    * Sets the activation status of the reference annotation.
903    * 
904    * @param status
905    */
906   public void setReferenceAnnotationStatus(boolean status)
907   {
908     fileProperties.put(REFERENCE_ANNOTATION, status ? YES : NO);
909   }
910
911   /**
912    * Sets the activation status of the mask value annotation.
913    * 
914    * @param status
915    */
916   public void setMaskedValueStatus(boolean status)
917   {
918     fileProperties.put(MASKED_VALUE, status ? YES : NO);
919   }
920
921   /**
922    * Sets the activation status of the consensus residue annotation.
923    * 
924    * @param status
925    */
926   public void setConsensusResidueStatus(boolean status)
927   {
928     fileProperties.put(CONSENSUS_RESIDUE, status ? YES : NO);
929   }
930
931   /**
932    * Sets the activation status of the consensus structure annotation.
933    * 
934    * @param status
935    */
936   public void setConsensusStructureStatus(boolean status)
937   {
938     fileProperties.put(CONSENSUS_STRUCTURE, status ? YES : NO);
939   }
940
941   /**
942    * Finds the index of the node in a hidden Markov model based on the column in
943    * the alignment
944    * 
945    * @param alignmentColumn
946    *          The index of the column in the alignment, with the indexes
947    *          starting from 0.
948    */
949
950   public Integer findNodeIndex(int alignmentColumn)
951   {
952     Integer index;
953     index = nodeLookup.get(alignmentColumn);
954     return index;
955   }
956
957   /**
958    * Finds the String values of a boolean. "yes" for true and "no" for false.
959    * 
960    * @param value
961    * @return
962    */
963   public static String findStringFromBoolean(boolean value)
964   {
965     if (value)
966     {
967       return YES;
968     }
969     else
970     {
971       return NO;
972     }
973   }
974
975
976
977   /**
978    * Returns the consensus sequence based on the most probable symbol at each
979    * position. The sequence is adjusted to match the length of the existing
980    * sequence alignment. Gap characters are used as padding.
981    * 
982    * @param length
983    *          The length of the longest sequence in the existing alignment.
984    * @return
985    */
986   public Sequence getConsensusSequence()
987   {
988     int start;
989     int end;
990     int modelLength;
991     start = getNodeAlignmentColumn(1);
992     modelLength = getLength();
993     end = getNodeAlignmentColumn(modelLength);
994     char[] sequence = new char[end + 1];
995     for (int index = 0; index < end + 1; index++)
996     {
997       Character character;
998
999         character = getConsensusAtAlignColumn(index);
1000
1001       if (character == null || character == '-')
1002       {
1003         sequence[index] = '-';
1004       }
1005       else
1006       {
1007         sequence[index] = Character.toUpperCase(character);
1008       }
1009       }
1010
1011
1012     Sequence seq = new Sequence(getName() + "_HMM", sequence, start,
1013             end);
1014     return seq;
1015   }
1016
1017
1018   /**
1019    * Initiates a HMM consensus sequence
1020    * 
1021    * @return A new HMM consensus sequence
1022    */
1023   public SequenceI initHMMSequence()
1024   {
1025     Sequence consensus = getConsensusSequence();
1026     consensus.setIsHMMConsensusSequence(true);
1027     consensus.setHMM(this);
1028     return consensus;
1029   }
1030
1031
1032 }
1033