Merge branch 'develop' into features/JAL-2393customMatrices
[jalview.git] / src / jalview / analysis / scoremodels / PIDModel.java
1 package jalview.analysis.scoremodels;
2
3 import jalview.api.analysis.PairwiseScoreModelI;
4 import jalview.api.analysis.SimilarityParamsI;
5 import jalview.api.analysis.SimilarityScoreModelI;
6 import jalview.datamodel.AlignmentView;
7 import jalview.math.Matrix;
8 import jalview.math.MatrixI;
9 import jalview.util.Comparison;
10
11 /**
12  * A class to provide sequence pairwise similarity based on residue identity
13  */
14 public class PIDModel implements SimilarityScoreModelI,
15         PairwiseScoreModelI
16 {
17   private static final String NAME = "PID";
18
19   private String description;
20
21   /**
22    * Constructor
23    */
24   public PIDModel()
25   {
26   }
27
28   @Override
29   public String getName()
30   {
31     return NAME;
32   }
33
34   @Override
35   public String getDescription()
36   {
37     return description;
38   }
39
40   @Override
41   public boolean isDNA()
42   {
43     return true;
44   }
45
46   @Override
47   public boolean isProtein()
48   {
49     return true;
50   }
51
52   /**
53    * Answers 1 if c and d are the same residue (ignoring case), and not gap
54    * characters. Answers 0 for non-matching or gap characters.
55    */
56   @Override
57   public float getPairwiseScore(char c, char d)
58   {
59     c = toUpper(c);
60     d = toUpper(d);
61     if (c == d && !Comparison.isGap(c))
62     {
63       return 1f;
64     }
65     return 0f;
66   }
67
68   /**
69    * @param c
70    */
71   protected static char toUpper(char c)
72   {
73     if ('a' <= c && c <= 'z')
74     {
75       c += 'A' - 'a';
76     }
77     return c;
78   }
79
80   @Override
81   public MatrixI findSimilarities(AlignmentView seqData,
82           SimilarityParamsI options)
83   {
84     String[] seqs = seqData.getSequenceStrings(Comparison.GAP_DASH);
85     return findSimilarities(seqs, options);
86   }
87
88   /**
89    * Compute percentage identity scores, using the gap treatment and
90    * normalisation specified by the options parameter
91    * 
92    * @param seqs
93    * @param options
94    * @return
95    */
96   protected MatrixI findSimilarities(String[] seqs,
97           SimilarityParamsI options)
98   {
99     // TODO reuse code in ScoreMatrix instead somehow
100     double[][] values = new double[seqs.length][];
101     for (int row = 0; row < seqs.length; row++)
102     {
103       values[row] = new double[seqs.length];
104       for (int col = 0; col < seqs.length; col++)
105       {
106         double total = computePID(seqs[row], seqs[col], options);
107         values[row][col] = total;
108       }
109     }
110     return new Matrix(values);
111   }
112
113   /**
114    * Computes a percentage identity for two sequences, using the algorithm
115    * choices specified by the options parameter
116    * 
117    * @param seq1
118    * @param seq2
119    * @param options
120    * @return
121    */
122   public static double computePID(String seq1, String seq2,
123           SimilarityParamsI options)
124   {
125     int len1 = seq1.length();
126     int len2 = seq2.length();
127     int width = Math.max(len1, len2);
128     int total = 0;
129     int divideBy = 0;
130
131     for (int i = 0; i < width; i++)
132     {
133       if (i >= len1 || i >= len2)
134       {
135         /*
136          * off the end of one sequence; stop if we are only matching
137          * on the shorter sequence length, else treat as trailing gap
138          */
139         if (options.denominateByShortestLength())
140         {
141           break;
142         }
143         if (options.includeGaps())
144         {
145           divideBy++;
146         }
147         if (options.matchGaps())
148         {
149           total++;
150         }
151         continue;
152       }
153       char c1 = seq1.charAt(i);
154       char c2 = seq2.charAt(i);
155       boolean gap1 = Comparison.isGap(c1);
156       boolean gap2 = Comparison.isGap(c2);
157
158       if (gap1 && gap2)
159       {
160         /*
161          * gap-gap: include if options say so, if so
162          * have to score as identity; else ignore
163          */
164         if (options.includeGappedColumns())
165         {
166           divideBy++;
167           total++;
168         }
169         continue;
170       }
171
172       if (gap1 || gap2)
173       {
174         /*
175          * gap-residue: include if options say so, 
176          * count as match if options say so
177          */
178         if (options.includeGaps())
179         {
180           divideBy++;
181         }
182         if (options.matchGaps())
183         {
184           total++;
185         }
186         continue;
187       }
188
189       /*
190        * remaining case is gap-residue
191        */
192       if (toUpper(c1) == toUpper(c2))
193       {
194         total++;
195       }
196       divideBy++;
197     }
198
199     return divideBy == 0 ? 0D : 100D * total / divideBy;
200   }
201 }