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