f4599e82f3ffcf4638d6bf848da7c24a1aff65f4
[jalview.git] / src / jalview / analysis / scoremodels / PIDModel.java
1 package jalview.analysis.scoremodels;
2
3 import jalview.api.AlignmentViewPanel;
4 import jalview.api.analysis.PairwiseScoreModelI;
5 import jalview.api.analysis.ScoreModelI;
6 import jalview.api.analysis.SimilarityParamsI;
7 import jalview.datamodel.AlignmentView;
8 import jalview.math.Matrix;
9 import jalview.math.MatrixI;
10 import jalview.util.Comparison;
11
12 /**
13  * A class to provide sequence pairwise similarity based on residue identity.
14  * Instances of this class are immutable and thread-safe, so the same object is
15  * returned from calls to getInstance().
16  */
17 public class PIDModel extends SimilarityScoreModel
18         implements PairwiseScoreModelI
19 {
20   private static final String NAME = "PID";
21
22   /**
23    * Constructor
24    */
25   public PIDModel()
26   {
27   }
28
29   @Override
30   public String getName()
31   {
32     return NAME;
33   }
34
35   /**
36    * Answers null for description. If a display name is needed, use getName() or
37    * an internationalized string built from the name.
38    */
39   @Override
40   public String getDescription()
41   {
42     return null;
43   }
44
45   @Override
46   public boolean isDNA()
47   {
48     return true;
49   }
50
51   @Override
52   public boolean isProtein()
53   {
54     return true;
55   }
56
57   /**
58    * Answers 1 if c and d are the same residue (ignoring case), and not gap
59    * characters. Answers 0 for non-matching or gap characters.
60    */
61   @Override
62   public float getPairwiseScore(char c, char d)
63   {
64     c = toUpper(c);
65     d = toUpper(d);
66     if (c == d && !Comparison.isGap(c))
67     {
68       return 1f;
69     }
70     return 0f;
71   }
72
73   /**
74    * @param c
75    */
76   protected static char toUpper(char c)
77   {
78     if ('a' <= c && c <= 'z')
79     {
80       c += 'A' - 'a';
81     }
82     return c;
83   }
84
85   /**
86    * Computes similarity scores based on pairwise percentage identity of
87    * sequences. For consistency with Jalview 2.10.1's SeqSpace mode PCA
88    * calculation, the percentage scores are rescaled to the width of the
89    * sequences (as if counts of identical residues). This method is thread-safe.
90    */
91   @Override
92   public MatrixI findSimilarities(AlignmentView seqData,
93           SimilarityParamsI options)
94   {
95     String[] seqs = seqData.getSequenceStrings(Comparison.GAP_DASH);
96
97     MatrixI result = findSimilarities(seqs, options);
98
99     result.multiply(seqData.getWidth() / 100d);
100
101     return result;
102   }
103
104   /**
105    * A distance score is computed in the usual way (by reversing the range of
106    * the similarity score results), and then rescaled to percentage values
107    * (reversing the rescaling to count values done in findSimilarities). This
108    * method is thread-safe.
109    */
110   @Override
111   public MatrixI findDistances(AlignmentView seqData,
112           SimilarityParamsI options)
113   {
114     MatrixI result = super.findDistances(seqData, options);
115
116     if (seqData.getWidth() != 0)
117     {
118       result.multiply(100d / seqData.getWidth());
119     }
120
121     return result;
122   }
123
124   /**
125    * Compute percentage identity scores, using the gap treatment and
126    * normalisation specified by the options parameter
127    * 
128    * @param seqs
129    * @param options
130    * @return
131    */
132   protected MatrixI findSimilarities(String[] seqs,
133           SimilarityParamsI options)
134   {
135     // TODO reuse code in ScoreMatrix instead somehow
136     double[][] values = new double[seqs.length][];
137     for (int row = 0; row < seqs.length; row++)
138     {
139       values[row] = new double[seqs.length];
140       for (int col = 0; col < seqs.length; col++)
141       {
142         double total = computePID(seqs[row], seqs[col], options);
143         values[row][col] = total;
144       }
145     }
146     return new Matrix(values);
147   }
148
149   /**
150    * Computes a percentage identity for two sequences, using the algorithm
151    * choices specified by the options parameter
152    * 
153    * @param seq1
154    * @param seq2
155    * @param options
156    * @return
157    */
158   public static double computePID(String seq1, String seq2,
159           SimilarityParamsI options)
160   {
161     int len1 = seq1.length();
162     int len2 = seq2.length();
163     int width = Math.max(len1, len2);
164     int total = 0;
165     int divideBy = 0;
166
167     for (int i = 0; i < width; i++)
168     {
169       if (i >= len1 || i >= len2)
170       {
171         /*
172          * off the end of one sequence; stop if we are only matching
173          * on the shorter sequence length, else treat as trailing gap
174          */
175         if (options.denominateByShortestLength())
176         {
177           break;
178         }
179         if (options.includeGaps())
180         {
181           divideBy++;
182         }
183         if (options.matchGaps())
184         {
185           total++;
186         }
187         continue;
188       }
189       char c1 = seq1.charAt(i);
190       char c2 = seq2.charAt(i);
191       boolean gap1 = Comparison.isGap(c1);
192       boolean gap2 = Comparison.isGap(c2);
193
194       if (gap1 && gap2)
195       {
196         /*
197          * gap-gap: include if options say so, if so
198          * have to score as identity; else ignore
199          */
200         if (options.includeGappedColumns())
201         {
202           divideBy++;
203           total++;
204         }
205         continue;
206       }
207
208       if (gap1 || gap2)
209       {
210         /*
211          * gap-residue: include if options say so, 
212          * count as match if options say so
213          */
214         if (options.includeGaps())
215         {
216           divideBy++;
217         }
218         if (options.matchGaps())
219         {
220           total++;
221         }
222         continue;
223       }
224
225       /*
226        * remaining case is gap-residue
227        */
228       if (toUpper(c1) == toUpper(c2))
229       {
230         total++;
231       }
232       divideBy++;
233     }
234
235     return divideBy == 0 ? 0D : 100D * total / divideBy;
236   }
237
238   @Override
239   public ScoreModelI getInstance(AlignmentViewPanel avp)
240   {
241     return this;
242   }
243 }