JAL-4114 alphafold now marked as AB-INITIO so adjust ranking in categories
[jalview.git] / src / jalview / gui / structurechooser / TDBResultAnalyser.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.gui.structurechooser;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.BitSet;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.List;
30 import java.util.Locale;
31
32 import jalview.datamodel.SequenceI;
33 import jalview.fts.api.FTSData;
34 import jalview.fts.core.FTSRestRequest;
35
36 public class TDBResultAnalyser
37 {
38
39   /**
40    * model categories - update as needed. warnings output if unknown types
41    * encountered.
42    * 
43    * Order denotes 'trust'
44    */
45   private static List<String> EXP_CATEGORIES = Arrays
46           .asList(new String[]
47           { "EXPERIMENTALLY DETERMINED", "DEEP-LEARNING", "AB-INITIO",
48               "TEMPLATE-BASED", "CONFORMATIONAL ENSEMBLE" });
49
50   private SequenceI seq;
51
52   private Collection<FTSData> collectedResults;
53
54   private FTSRestRequest lastTdbRequest;
55
56   private int idx_ups;
57
58   private int idx_upe;
59
60   private int idx_mcat;
61
62   private int idx_mqual;
63
64   private int idx_resol;
65
66   /**
67    * selection model
68    */
69   private String filter = null;
70
71   /**
72    * limit to particular source
73    */
74   private String sourceFilter = null;
75
76   private int idx_mprov;
77
78   public TDBResultAnalyser(SequenceI seq,
79           Collection<FTSData> collectedResults,
80           FTSRestRequest lastTdbRequest, String fieldToFilterBy,
81           String string)
82   {
83     this.seq = seq;
84     this.collectedResults = collectedResults;
85     this.lastTdbRequest = lastTdbRequest;
86     this.filter = fieldToFilterBy;
87     this.sourceFilter = string;
88     idx_ups = lastTdbRequest.getFieldIndex("Uniprot Start");
89     idx_upe = lastTdbRequest.getFieldIndex("Uniprot End");
90     idx_mcat = lastTdbRequest.getFieldIndex("Model Category");
91     idx_mprov = lastTdbRequest.getFieldIndex("Provider");
92     idx_mqual = lastTdbRequest.getFieldIndex("Confidence");
93     idx_resol = lastTdbRequest.getFieldIndex("Resolution");
94   }
95
96   /**
97    * maintain and resolve categories to 'trust order' TODO: change the trust
98    * scheme to something comprehensible.
99    * 
100    * @param cat
101    * @return 0 for null cat, less than zero for others
102    */
103   public final int scoreCategory(String cat)
104   {
105     if (cat == null)
106     {
107       return 0;
108     }
109     String upper_cat = cat.toUpperCase(Locale.ROOT);
110     int idx = EXP_CATEGORIES.indexOf(upper_cat);
111     if (idx == -1)
112     {
113       System.out.println("Unknown category: '" + cat + "'");
114       EXP_CATEGORIES.add(upper_cat);
115       idx = EXP_CATEGORIES.size() - 1;
116     }
117     return -EXP_CATEGORIES.size() - idx;
118   }
119
120   /**
121    * sorts records discovered by 3D beacons and excludes any that don't
122    * intersect with the sequence's start/end rage
123    * 
124    * @return
125    */
126   public List<FTSData> getFilteredResponse()
127   {
128     List<FTSData> filteredResponse = new ArrayList<FTSData>();
129
130     // ignore anything outside the sequence region
131     for (FTSData row : collectedResults)
132     {
133       if (row.getSummaryData() != null
134               && row.getSummaryData()[idx_ups] != null)
135       {
136         int up_s = (Integer) row.getSummaryData()[idx_ups];
137         int up_e = (Integer) row.getSummaryData()[idx_upe];
138         String provider = (String) row.getSummaryData()[idx_mprov];
139         String mcat = (String) row.getSummaryData()[idx_mcat];
140         // this makes sure all new categories are in the score array.
141         int scorecat = scoreCategory(mcat);
142         if (sourceFilter == null || sourceFilter.equals(provider))
143         {
144           if (seq == row.getSummaryData()[0] && up_e > seq.getStart()
145                   && up_s < seq.getEnd())
146           {
147             filteredResponse.add(row);
148           }
149         }
150       }
151     }
152     // sort according to decreasing length,
153     // increasing start
154     Collections.sort(filteredResponse, new Comparator<FTSData>()
155     {
156       @Override
157       public int compare(FTSData o1, FTSData o2)
158       {
159         Object[] o1data = o1.getSummaryData();
160         Object[] o2data = o2.getSummaryData();
161         int o1_s = (Integer) o1data[idx_ups];
162         int o1_e = (Integer) o1data[idx_upe];
163         int o1_cat = scoreCategory((String) o1data[idx_mcat]);
164         String o1_prov = ((String) o1data[idx_mprov])
165                 .toUpperCase(Locale.ROOT);
166         int o2_s = (Integer) o2data[idx_ups];
167         int o2_e = (Integer) o2data[idx_upe];
168         int o2_cat = scoreCategory((String) o2data[idx_mcat]);
169         String o2_prov = ((String) o2data[idx_mprov])
170                 .toUpperCase(Locale.ROOT);
171
172         if (o1_cat == o2_cat)
173         {
174           if (o1_s == o2_s)
175           {
176             int o1_xtent = o1_e - o1_s;
177             int o2_xtent = o2_e - o2_s;
178             if (o1_xtent == o2_xtent)
179             {
180               // EXPERIMENTAL DATA ALWAYS TRUMPS MODELS
181               if (o1_cat == scoreCategory(EXP_CATEGORIES.get(0)))
182               {
183                 if (o1_prov.equals(o2_prov))
184                 {
185                   if ("PDBE".equals(o1_prov))
186                   {
187                     if (eitherNull(idx_resol, o1data, o2data))
188                     {
189                       return nonNullFirst(idx_resol, o1data, o2data);
190                     }
191                     // experimental structures, so rank on quality
192                     double o1_res = (Double) o1data[idx_resol];
193                     double o2_res = (Double) o2data[idx_resol];
194                     return (o2_res < o1_res) ? 1
195                             : (o2_res == o1_res) ? 0 : -1;
196                   }
197                   else
198                   {
199                     return 0; // no change in order
200                   }
201                 }
202                 else
203                 {
204                   // PDBe always ranked above all other experimentally
205                   // determined categories
206                   return "PDBE".equals(o1_prov) ? -1
207                           : "PDBE".equals(o2_prov) ? 1 : 0;
208                 }
209               }
210               else
211               {
212                 if (eitherNull(idx_mqual, o1data, o2data))
213                 {
214                   return nonNullFirst(idx_mqual, o1data, o2data);
215                 }
216                 // models, so rank on qmean - b
217                 double o1_mq = (Double) o1data[idx_mqual];
218                 double o2_mq = (Double) o2data[idx_mqual];
219                 return (o2_mq < o1_mq) ? 1 : (o2_mq == o1_mq) ? 0 : -1;
220               }
221             }
222             else
223             {
224               return o1_xtent - o2_xtent;
225             }
226           }
227           else
228           {
229             return o1_s - o2_s;
230           }
231         }
232         else
233         {
234           // if both are not experimental, then favour alphafold
235           if (o2_cat > 0 && o1_cat > 0)
236           {
237             return "ALPHAFOLD DB".equals(o1_prov) ? -1
238                     : "ALPHAFOLD DB".equals(o2_prov) ? 1 : 0;
239           }
240           return o2_cat - o1_cat;
241         }
242       }
243
244       private int nonNullFirst(int idx_resol, Object[] o1data,
245               Object[] o2data)
246       {
247         return o1data[idx_resol] == o2data[idx_resol] ? 0
248                 : o1data[idx_resol] != null ? -1 : 1;
249       }
250
251       private boolean eitherNull(int idx_resol, Object[] o1data,
252               Object[] o2data)
253       {
254         return (o1data[idx_resol] == null || o2data[idx_resol] == null);
255       }
256
257       @Override
258       public boolean equals(Object obj)
259       {
260         return super.equals(obj);
261       }
262     });
263     return filteredResponse;
264   }
265
266   /**
267    * return list of structures to be marked as selected for this sequence
268    * according to given criteria
269    * 
270    * @param filteredStructures
271    *          - sorted, filtered structures from getFilteredResponse
272    * 
273    */
274   public List<FTSData> selectStructures(List<FTSData> filteredStructures)
275   {
276     List<FTSData> selected = new ArrayList<FTSData>();
277     BitSet cover = new BitSet();
278     cover.set(seq.getStart(), seq.getEnd());
279     // walk down the list of structures, selecting some to add to selected
280     // TODO: could do simple DP - double loop to select largest number of
281     // structures covering largest number of sites
282     for (FTSData structure : filteredStructures)
283     {
284       Object[] odata = structure.getSummaryData();
285       int o1_s = (Integer) odata[idx_ups];
286       int o1_e = (Integer) odata[idx_upe];
287       int o1_cat = scoreCategory((String) odata[idx_mcat]);
288       BitSet scover = new BitSet();
289       // measure intersection
290       scover.set(o1_s, o1_e);
291       scover.and(cover);
292       if (scover.cardinality() > 4)
293       {
294         selected.add(structure);
295         // clear the range covered by this structure
296         cover.andNot(scover);
297       }
298     }
299     if (selected.size() == 0)
300     {
301       return selected;
302     }
303     // final step is to sort on length - this might help the superposition
304     // process
305     Collections.sort(selected, new Comparator<FTSData>()
306     {
307       @Override
308       public int compare(FTSData o1, FTSData o2)
309       {
310         Object[] o1data = o1.getSummaryData();
311         Object[] o2data = o2.getSummaryData();
312         int o1_xt = ((Integer) o1data[idx_upe])
313                 - ((Integer) o1data[idx_ups]);
314         int o1_cat = scoreCategory((String) o1data[idx_mcat]);
315         int o2_xt = ((Integer) o2data[idx_upe] - (Integer) o2data[idx_ups]);
316         int o2_cat = scoreCategory((String) o2data[idx_mcat]);
317         return o2_xt - o1_xt;
318       }
319     });
320     if (filter.equals(
321             ThreeDBStructureChooserQuerySource.FILTER_FIRST_BEST_COVERAGE))
322     {
323       return selected.subList(0, 1);
324     }
325     return selected;
326   }
327
328 }