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