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