JAL-4134 JAL-2349 test & fixed implementation(ish) for getMappedPositionsFor(seq...
[jalview.git] / src / jalview / ws / datamodel / alphafold / MappableContactMatrix.java
1 package jalview.ws.datamodel.alphafold;
2
3 import java.awt.Color;
4 import java.util.ArrayList;
5 import java.util.BitSet;
6
7 import jalview.datamodel.ContactListI;
8 import jalview.datamodel.ContactListImpl;
9 import jalview.datamodel.ContactListProviderI;
10 import jalview.datamodel.GroupSet;
11 import jalview.datamodel.GroupSetI;
12 import jalview.datamodel.Mapping;
13 import jalview.datamodel.SequenceI;
14 import jalview.util.MapList;
15 import jalview.ws.datamodel.MappableContactMatrixI;
16
17 public abstract class MappableContactMatrix<T extends MappableContactMatrix<T>>
18         implements MappableContactMatrixI
19 {
20   SequenceI refSeq = null;
21
22   MapList toSeq = null;
23
24   /**
25    * the length that refSeq is expected to be (excluding gaps, of course)
26    */
27   int length;
28
29   @Override
30   public boolean hasReferenceSeq()
31   {
32     return (refSeq != null);
33   }
34
35   @Override
36   public SequenceI getReferenceSeq()
37   {
38     return refSeq;
39   }
40
41   /**
42    * container for groups - defined on matrix columns
43    */
44   GroupSet grps = new GroupSet();
45
46   @Override
47   public GroupSetI getGroupSet()
48   {
49     return grps;
50   };
51
52   @Override
53   public void setGroupSet(GroupSet makeGroups)
54   {
55     grps = makeGroups;
56   }
57
58   @Override
59   public MapList getMapFor(SequenceI mapSeq)
60   {
61     if (refSeq != null)
62     {
63       while (mapSeq != refSeq && mapSeq.getDatasetSequence() != null)
64       {
65         mapSeq = mapSeq.getDatasetSequence();
66       }
67       if (mapSeq != refSeq)
68       {
69         return null;
70       }
71     }
72     else
73     {
74       if (mapSeq != null)
75       {
76         // our MapList does not concern this seq
77         return null;
78       }
79     }
80
81     return toSeq;
82   }
83
84   /**
85    * set the reference sequence and construct the mapping between the start-end
86    * positions of given sequence and row/columns of contact matrix
87    * 
88    * @param _refSeq
89    */
90   public void setRefSeq(SequenceI _refSeq)
91   {
92     refSeq = _refSeq;
93     while (refSeq.getDatasetSequence() != null)
94     {
95       refSeq = refSeq.getDatasetSequence();
96     }
97     length = _refSeq.getEnd() - _refSeq.getStart() + 1;
98     // if (length!=refSeq.getLength() || _refSeq.getStart()!=1)
99     {
100       toSeq = new MapList(
101               new int[]
102               { _refSeq.getStart(), _refSeq.getEnd() },
103               new int[]
104               { 0, length - 1 }, 1, 1);
105     }
106   }
107
108   public T liftOver(SequenceI newRefSeq, Mapping sp2sq)
109   {
110     if (sp2sq.getMappedWidth() != sp2sq.getWidth())
111     {
112       // TODO: employ getWord/MappedWord to transfer annotation between cDNA and
113       // Protein reference frames
114       throw new Error(
115               "liftOver currently not implemented for transfer of annotation between different types of seqeunce");
116     }
117     boolean mapIsTo = (sp2sq != null) ? (sp2sq.getTo() == refSeq) : false;
118
119     /**
120      * map from matrix to toSeq's coordinate frame
121      */
122     int[] refMap = toSeq.locateInFrom(0, length - 1);
123     ArrayList<Integer> newFromMap = new ArrayList<Integer>();
124     int last = -1;
125     for (int i = 0; i < refMap.length; i += 2)
126     {
127       /*
128        * for each contiguous range in toSeq, locate corresponding range in sequence mapped to toSeq by sp2sq
129        */
130       int[] sp2map = mapIsTo
131               ? sp2sq.getMap().locateInFrom(refMap[i], refMap[i + 1])
132               : sp2sq.getMap().locateInTo(refMap[i], refMap[i + 1]);
133       if (sp2map == null)
134       {
135         continue;
136       }
137
138       for (int spm = 0; spm < sp2map.length; spm += 2)
139       {
140
141         if (last > -1)
142         {
143           if (sp2map[spm] != last + 1)
144           {
145             newFromMap.add(sp2map[spm]);
146           }
147           else
148           {
149             newFromMap.remove(newFromMap.size() - 1);
150           }
151         }
152         else
153         {
154           newFromMap.add(sp2map[spm]);
155         }
156         last = sp2map[spm + 1];
157         newFromMap.add(last);
158       }
159     }
160     if ((newFromMap.size() % 2) != 0)
161     {
162       // should have had an even number of int ranges!
163       throw new Error("PAEMatrix liftover failed.");
164     }
165     int fromIntMap[] = new int[newFromMap.size()];
166     int ipos = 0;
167     for (Integer i : newFromMap)
168     {
169       fromIntMap[ipos++] = i;
170     }
171     MapList newFromMapList = new MapList(fromIntMap,
172             new int[]
173             { 0, length - 1 }, 1, 1);
174
175     T newCM = newMappableContactMatrix(newRefSeq, newFromMapList);
176     return newCM;
177   }
178
179   protected abstract T newMappableContactMatrix(SequenceI newRefSeq,
180           MapList newFromMapList);
181
182   @Override
183   public int[] getMappedPositionsFor(final SequenceI localFrame,
184           final int column)
185   {
186     return getMappedPositionsFor(localFrame, column, column);
187   }
188
189   public int[] getMappedPositionsFor(final SequenceI localFrame, int from,
190           int to)
191   {
192     if (localFrame == null)
193     {
194       throw new Error("Unimplemented when no local sequence given.");
195     }
196     SequenceI lf = localFrame, uf = refSeq;
197
198     // check that localFrame is derived from refSeq
199     // just look for dataset sequences and check they are the same.
200     // in future we could use DBRefMappings/whatever.
201     while (lf.getDatasetSequence() != null
202             || uf.getDatasetSequence() != null)
203     {
204       if (lf.getDatasetSequence() != null)
205       {
206         lf = lf.getDatasetSequence();
207       }
208       if (uf.getDatasetSequence() != null)
209       {
210         uf = uf.getDatasetSequence();
211       }
212     }
213     if (lf != uf)
214     {
215       // could try harder to find a mapping
216       throw new Error("This Matrix associated with '" + refSeq.getName()
217               + "' is not mappable for the given localFrame sequence. ("
218               + localFrame.getName() + ")");
219     }
220     
221     // now look up from-to matrix columns in toSeq frame
222     
223     if (toSeq == null)
224     {
225       // no mapping - so we assume 1:1
226       return new int[] { from, to };
227     }
228     // from-to are matrix columns
229     // first locate on reference sequence
230
231     int[] mappedPositions = toSeq.locateInFrom(from, to);
232     if (mappedPositions==null)
233     {
234       return null;
235     }
236     
237     // and now map to localFrame
238     // from-to columns on the associated sequence should be
239     // i. restricted to positions in localFrame
240     // ii. 
241
242 //    int s = -1, e = -1;
243 //    for (int p = 0; p < mappedPositions.length; p++)
244 //    {
245 //      if (s == -1 && mappedPositions[p] >= localFrame.getStart())
246 //      {
247 //        s = p; // remember first position within local frame
248 //      }
249 //      if (e == -1 || mappedPositions[p] <= localFrame.getEnd())
250 //      {
251 //        // update end pointer
252 //        e = p;
253 //        // compute local map
254 //        mappedPositions[p] = localFrame.findIndex(mappedPositions[p]);
255 //      }
256 //    }
257 //    int[] _trimmed = new int[e - s + 1];
258 //    return _trimmed;
259     return mappedPositions;
260   }
261
262   @Override
263   public ContactListI getMappableContactList(final SequenceI localFrame,
264           final int column)
265   {
266     final int _column;
267     final int _lcolumn;
268     if (localFrame == null)
269     {
270       throw new Error("Unimplemented when no local sequence given.");
271     }
272     // return a ContactListI for column
273     // column is index into localFrame
274     // 1. map column to corresponding column in matrix
275     final MappableContactMatrix us = this;
276     _lcolumn = localFrame.findPosition(column);
277
278     if (toSeq != null)
279     {
280       SequenceI lf = localFrame, uf = refSeq;
281
282       // just look for dataset sequences and check they are the same.
283       // in future we could use DBRefMappings/whatever.
284       while (lf.getDatasetSequence() != null
285               || uf.getDatasetSequence() != null)
286       {
287         if (lf.getDatasetSequence() != null)
288         {
289           lf = lf.getDatasetSequence();
290         }
291         if (uf.getDatasetSequence() != null)
292         {
293           uf = uf.getDatasetSequence();
294         }
295       }
296       if (lf != uf)
297       {
298         // could try harder to find a mapping
299         throw new Error("This Matrix associated with '" + refSeq.getName()
300                 + "' is not mappable for the given localFrame sequence. ("
301                 + localFrame.getName() + ")");
302       }
303       // check the mapping to see if localFrame _lcolumn exists
304       int[] word = toSeq.locateInTo(_lcolumn, _lcolumn);
305       if (word == null)
306       {
307         return null;
308       }
309       _column = word[0];
310     }
311     else
312     {
313       // no mapping
314       _column = _lcolumn;
315     }
316
317     // TODO - remove ? this may be a redundant check
318     if (_column < 0 || ((toSeq != null && _column > toSeq.getToHighest())
319             || (toSeq == null && getHeight() <= _column)))
320     {
321       return null;
322     }
323
324     // 2. resolve ranges in matrix corresponding to range in localFrame
325     final int[] matrixRange = toSeq == null
326             ? new int[]
327             { localFrame.getStart(), localFrame.getEnd() }
328             : toSeq.locateInTo(localFrame.getStart(), localFrame.getEnd());
329
330     int h = 0;
331     for (int p = 0; p < matrixRange.length; p += 2)
332     {
333       h += 1 + Math.abs(matrixRange[p + 1] - matrixRange[p]);
334     }
335     final int rangeHeight = h;
336     // 3. Construct ContactListImpl instance for just those segments.
337
338     return new ContactListImpl(new ContactListProviderI()
339     {
340
341       public int getColumn()
342       {
343         return column;
344       }
345
346       @Override
347       public int getPosition()
348       {
349         return _column;
350       }
351
352       @Override
353       public int getContactHeight()
354       {
355         return rangeHeight;
356       }
357
358       @Override
359       public double getContactAt(int mcolumn)
360       {
361         if (mcolumn < 0 || mcolumn >= rangeHeight)
362         {
363           return -1;
364         }
365         return getElementAt(_column, locateInRange(mcolumn));
366
367         // this code maps from mcolumn to localFrame - but that isn't what's
368         // needed
369         // int loccolumn = localFrame.findPosition(mcolumn);
370         // int[] lcolumn=(toSeq==null) ? new int[] {mcolumn} :
371         // toSeq.locateInTo(loccolumn,loccolumn);
372         // if (lcolumn==null || lcolumn[0] < 0 || lcolumn[0] >= rangeHeight)
373         // {
374         // return -1;
375         // }
376         // return getElementAt(_column,lcolumn[0]);
377       }
378
379       @Override
380       public int[] getMappedPositionsFor(int cStart, int cEnd)
381       {
382         if (!hasReferenceSeq())
383         {
384           return ContactListProviderI.super.getMappedPositionsFor(cStart,
385                   cEnd);
386         }
387         // map into segment of matrix being shown
388         int realCstart = locateInRange(cStart);
389         int realCend = locateInRange(cEnd);
390
391         // TODO account for discontinuities in the mapping
392
393         int[] mappedPositions = toSeq.locateInFrom(realCstart, realCend);
394         if (mappedPositions != null)
395         {
396           int s = -1, e = -1;
397           for (int p = 0; p < mappedPositions.length; p++)
398           {
399             if (s == -1 && mappedPositions[p] >= localFrame.getStart())
400             {
401               s = p; // remember first position within local frame
402             }
403             if (e == -1 || mappedPositions[p] <= localFrame.getEnd())
404             {
405               // update end pointer
406               e = p;
407               // compute local map
408               mappedPositions[p] = localFrame.findIndex(mappedPositions[p]);
409             }
410           }
411         }
412         return mappedPositions;
413       }
414
415       /**
416        * @return the mcolumn'th position in the matrixRange window on the matrix
417        */
418       private int locateInRange(int mcolumn)
419       {
420
421         int h = 0, p = 0;
422         while (h < mcolumn && p + 2 < matrixRange.length)
423         {
424           h += 1 + Math.abs(matrixRange[p + 1] - matrixRange[p]);
425           p += 2;
426         }
427         return matrixRange[p] + mcolumn - h;
428       }
429
430       @Override
431       public Color getColourForGroup()
432       {
433         BitSet gp = us.getGroupsFor(_column);
434         Color col = us.getColourForGroup(gp);
435         return col;
436       }
437     });
438   }
439
440   /**
441    * get a specific element of the contact matrix in its data-local coordinates
442    * rather than the mapped frame. Implementations are allowed to throw
443    * RunTimeExceptions if _column/i are out of bounds
444    * 
445    * @param _column
446    * @param i
447    * @return
448    */
449   protected abstract double getElementAt(int _column, int i);
450
451 }