JAL-2403 ScoreModelI now DistanceModelI, ScoreMatrix delegate of
[jalview.git] / src / jalview / analysis / scoremodels / ScoreMatrix.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.analysis.scoremodels;
22
23 import java.util.Arrays;
24
25 public class ScoreMatrix implements PairwiseScoreModelI
26 {
27   public static final short UNMAPPED = (short) -1;
28
29   private static final String BAD_ASCII_ERROR = "Unexpected character %s in getPairwiseScore";
30
31   private static final int MAX_ASCII = 127;
32
33   /*
34    * the name of the model as shown in menus
35    */
36   private String name;
37
38   /*
39    * the characters that the model provides scores for
40    */
41   private char[] symbols;
42
43   /*
44    * the score matrix; both dimensions must equal the number of symbols
45    * matrix[i][j] is the substitution score for replacing symbols[i] with symbols[j]
46    */
47   private float[][] matrix;
48
49   /*
50    * quick lookup to convert from an ascii character value to the index
51    * of the corresponding symbol in the score matrix 
52    */
53   private short[] symbolIndex;
54
55   /*
56    * true for Protein Score matrix, false for dna score matrix
57    */
58   private boolean peptide;
59
60   /**
61    * Constructor
62    * 
63    * @param name
64    *          Unique, human readable name for the matrix
65    * @param alphabet
66    *          the symbols to which scores apply
67    * @param matrix
68    *          Pairwise scores indexed according to the symbol alphabet
69    */
70   public ScoreMatrix(String name, char[] alphabet, float[][] matrix)
71   {
72     this.matrix = matrix;
73     this.name = name;
74     this.symbols = alphabet;
75
76     symbolIndex = buildSymbolIndex(alphabet);
77
78     /*
79      * crude heuristic for now...
80      */
81     peptide = alphabet.length >= 20;
82   }
83
84   /**
85    * Returns an array A where A[i] is the position in the alphabet array of the
86    * character whose value is i. For example if the alphabet is { 'A', 'D', 'X'
87    * } then A['D'] = A[68] = 1.
88    * <p>
89    * Unmapped characters (not in the alphabet) get an index of -1.
90    * <p>
91    * Mappings are added automatically for lower case symbols (for non case
92    * sensitive scoring), unless they are explicitly present in the alphabet (are
93    * scored separately in the score matrix).
94    * 
95    * @param alphabet
96    * @return
97    */
98   static short[] buildSymbolIndex(char[] alphabet)
99   {
100     short[] index = new short[MAX_ASCII + 1];
101     Arrays.fill(index, UNMAPPED);
102     short pos = 0;
103     for (char c : alphabet)
104     {
105       if (c <= MAX_ASCII)
106       {
107         index[c] = pos;
108       }
109
110       /*
111        * also map lower-case character (unless separately mapped)
112        */
113       if (c >= 'A' && c <= 'Z')
114       {
115         short lowerCase = (short) (c + ('a' - 'A'));
116         if (index[lowerCase] == UNMAPPED)
117         {
118           index[lowerCase] = pos;
119         }
120       }
121       pos++;
122     }
123     return index;
124   }
125
126   @Override
127   public String getName()
128   {
129     return name;
130   }
131
132   @Override
133   public boolean isDNA()
134   {
135     return !peptide;
136   }
137
138   @Override
139   public boolean isProtein()
140   {
141     return peptide;
142   }
143
144   /**
145    * Returns the score matrix as used in getPairwiseScore. If using this matrix
146    * directly, callers <em>must</em> also call <code>getMatrixIndex</code> in
147    * order to get the matrix index for each character (symbol).
148    * 
149    * @return
150    * @see #getMatrixIndex(char)
151    */
152   public float[][] getMatrix()
153   {
154     return matrix;
155   }
156
157   /**
158    * Answers the matrix index for a given character, or -1 if unmapped in the
159    * matrix. Use this method only if using <code>getMatrix</code> in order to
160    * compute scores directly (without symbol lookup) for efficiency.
161    * 
162    * @param c
163    * @return
164    * @see #getMatrix()
165    */
166   public int getMatrixIndex(char c)
167   {
168     if (c < symbolIndex.length)
169     {
170       return symbolIndex[c];
171     }
172     else
173     {
174       return UNMAPPED;
175     }
176   }
177
178   /**
179    * Returns the pairwise score for substituting c with d, or zero if c or d is
180    * an unscored or unexpected character
181    */
182   @Override
183   public float getPairwiseScore(char c, char d)
184   {
185     if (c >= symbolIndex.length)
186     {
187       System.err.println(String.format(BAD_ASCII_ERROR, c));
188       return 0;
189     }
190     if (d >= symbolIndex.length)
191     {
192       System.err.println(String.format(BAD_ASCII_ERROR, d));
193       return 0;
194     }
195
196     int cIndex = symbolIndex[c];
197     int dIndex = symbolIndex[d];
198     if (cIndex != UNMAPPED && dIndex != UNMAPPED)
199     {
200       return matrix[cIndex][dIndex];
201     }
202     return 0;
203   }
204
205   /**
206    * pretty print the matrix
207    */
208   @Override
209   public String toString()
210   {
211     return outputMatrix(false);
212   }
213
214   /**
215    * Print the score matrix, optionally formatted as html, with the alphabet symbols as column headings and at the start of each row
216    * @param html
217    * @return
218    */
219   public String outputMatrix(boolean html)
220   {
221     StringBuilder sb = new StringBuilder(512);
222
223     /*
224      * heading row with alphabet
225      */
226     if (html)
227     {
228       sb.append("<table border=\"1\">");
229       sb.append(html ? "<tr><th></th>" : "");
230     }
231     for (char sym : symbols)
232     {
233       if (html)
234       {
235         sb.append("<th>&nbsp;").append(sym).append("&nbsp;</th>");
236       }
237       else
238       {
239         sb.append("\t").append(sym);
240       }
241     }
242     sb.append(html ? "</tr>\n" : "\n");
243
244     /*
245      * table of scores
246      */
247     for (char c1 : symbols)
248     {
249       if (html)
250       {
251         sb.append("<tr><td>");
252       }
253       sb.append(c1).append(html ? "</td>" : "");
254       for (char c2 : symbols)
255       {
256         sb.append(html ? "<td>" : "\t")
257                 .append(matrix[symbolIndex[c1]][symbolIndex[c2]])
258                 .append(html ? "</td>" : "");
259       }
260       sb.append(html ? "</tr>\n" : "\n");
261     }
262     if (html)
263     {
264       sb.append("</table>");
265     }
266     return sb.toString();
267   }
268 }