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