moving word methods to MapList and begin bug-fix for single-residue codon span featur...
[jalview.git] / src / jalview / util / MapList.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.util;
20
21 import java.util.*;
22
23 /**
24  * MapList
25  * Simple way of bijectively mapping a non-contiguous linear range to another non-contiguous linear range
26  * Use at your own risk!
27  * TODO: efficient implementation of private posMap method
28  * TODO: test/ensure that sense of from and to ratio start position is conserved (codon start position recovery)
29  * TODO: optimize to use int[][] arrays rather than vectors.
30  */
31 public class MapList
32 {
33   /* (non-Javadoc)
34    * @see java.lang.Object#equals(java.lang.Object)
35    */
36   public boolean equals(MapList obj) {
37     if (obj==this)
38       return true;
39     if (obj!=null && obj.fromRatio==fromRatio && obj.toRatio==toRatio
40         && obj.fromShifts!=null && obj.toShifts!=null) {
41       int i,iSize=fromShifts.size(),j,jSize=obj.fromShifts.size();
42       if (iSize!=jSize)
43         return false;
44       for (i=0,iSize=fromShifts.size(),j=0, jSize=obj.fromShifts.size(); i<iSize;) {
45         int[] mi=(int[]) fromShifts.elementAt(i++);
46         int[] mj=(int[]) obj.fromShifts.elementAt(j++);
47         if (mi[0]!=mj[0] || mi[1]!=mj[1])
48           return false;
49       }
50       iSize=toShifts.size();
51       jSize=obj.toShifts.size();
52       if (iSize!=jSize)
53         return false;
54       for (i=0,j=0; i<iSize;) {
55         int[] mi=(int[]) toShifts.elementAt(i++);
56         int[] mj=(int[]) obj.toShifts.elementAt(j++);
57         if (mi[0]!=mj[0] || mi[1]!=mj[1])
58           return false;
59       }
60       return true;
61     }
62     return false;
63   }
64   public Vector fromShifts;
65   public Vector toShifts;
66   int fromRatio; // number of steps in fromShifts to one toRatio unit
67   int toRatio; // number of steps in toShifts to one fromRatio
68   /**
69    * lowest and highest value in the from Map
70    */
71   int[] fromRange=null;
72   /**
73    * lowest and highest value in the to Map
74    */
75   int[] toRange=null;
76   public int getFromRatio()
77   {
78     return fromRatio;
79   }
80   public int getToRatio()
81   {
82     return toRatio;
83   }
84   public int getFromLowest() {
85     return fromRange[0];
86   }
87   public int getFromHighest() {
88     return fromRange[1];
89   }
90   public int getToLowest() {
91     return toRange[0];
92   }
93   public int getToHighest() {
94     return toRange[1];
95   }
96   private void ensureRange(int[] limits, int pos) {
97     if (limits[0]>pos)
98       limits[0]=pos;
99     if (limits[1]<pos)
100       limits[1]=pos;
101   }
102   public MapList(int from[], int to[], int fromRatio, int toRatio)
103   {
104     fromRange=new int[] { from[0],from[1] };
105     toRange=new int[] { to[0],to[1] };
106
107     fromShifts = new Vector();
108     for (int i=0;i<from.length; i+=2)
109     {
110       ensureRange(fromRange, from[i]);
111       ensureRange(fromRange, from[i+1]);
112
113       fromShifts.addElement(new int[]
114                              {from[i], from[i + 1]});
115     }
116     toShifts = new Vector();
117     for (int i=0;i<to.length; i+=2)
118     {
119       ensureRange(toRange, to[i]);
120       ensureRange(toRange, to[i+1]);
121       toShifts.addElement(new int[]
122                            {to[i], to[i + 1]});
123     }
124     this.fromRatio=fromRatio;
125     this.toRatio=toRatio;
126   }
127   public MapList(MapList map)
128   {
129     this.fromRange = new int[]
130     { map.fromRange[0], map.fromRange[1] };
131     this.toRange = new int[]
132     { map.toRange[0], map.toRange[1] };
133     this.fromRatio = map.fromRatio;
134     this.toRatio = map.toRatio;
135     if (map.fromShifts != null)
136     {
137       this.fromShifts = new Vector();
138       Enumeration e = map.fromShifts.elements();
139       while (e.hasMoreElements())
140       {
141         int[] el = (int[]) e.nextElement();
142         fromShifts.addElement(new int[]
143         { el[0], el[1] });
144       }
145     }
146     if (map.toShifts != null)
147     {
148       this.toShifts = new Vector();
149       Enumeration e = map.toShifts.elements();
150       while (e.hasMoreElements())
151       {
152         int[] el = (int[]) e.nextElement();
153         toShifts.addElement(new int[]
154         { el[0], el[1] });
155       }
156     }
157   }
158   /**
159    * get all mapped positions from 'from' to 'to'
160    * @return int[][] { int[] { fromStart, fromFinish, toStart, toFinish }, int [fromFinish-fromStart+2] { toStart..toFinish mappings}}
161    */
162   public int[][] makeFromMap()
163   {
164     return posMap(fromShifts, fromRatio, toShifts, toRatio);
165   }
166   /**
167    * get all mapped positions from 'to' to 'from'
168    * @return int[to position]=position mapped in from
169    */
170   public int[][] makeToMap()
171   {
172     return posMap(toShifts,toRatio, fromShifts, fromRatio);
173   }
174   /**
175    * construct an int map for intervals in intVals
176    * @param intVals
177    * @return int[] { from, to pos in range }, int[range.to-range.from+1] returning mapped position
178    */
179   private int[][] posMap(Vector intVals, int ratio, Vector toIntVals,
180       int toRatio)
181   {
182     int iv=0,ivSize = intVals.size();
183     if (iv>=ivSize)
184     {
185       return null;
186     }
187     int[] intv=(int[]) intVals.elementAt(iv++);
188     int from=intv[0],to=intv[1];
189     if (from > to)
190     {
191       from = intv[1];
192       to=intv[0];
193     }
194     while (iv<ivSize)
195     {
196       intv = (int[]) intVals.elementAt(iv++);
197       if (intv[0]<from)
198       {
199         from=intv[0];
200       }
201       if (intv[1]<from)
202       {
203         from=intv[1];
204       }
205       if (intv[0]>to)
206       {
207         to=intv[0];
208       }
209       if (intv[1]>to)
210       {
211         to=intv[1];
212       }
213     }
214     int tF=0,tT=0;
215     int mp[][] = new int[to-from+2][];
216     for (int i = 0; i < mp.length; i++)
217     {
218       int[] m = shift(i+from,intVals,ratio,toIntVals, toRatio);
219       if (m != null)
220       {
221         if (i == 0)
222         {
223           tF=tT=m[0];
224         }
225         else
226         {
227           if (m[0] < tF)
228           {
229             tF=m[0];
230           }
231           if (m[0] > tT)
232           {
233             tT=m[0];
234           }
235         }
236       }
237       mp[i] = m;
238     }
239     int[][] map = new int[][]
240                             {
241         new int[]
242                 {
243             from, to, tF, tT}, new int[to - from + 2]};
244
245     map[0][2] = tF;
246     map[0][3] = tT;
247
248     for (int i = 0; i < mp.length; i++)
249     {
250       if (mp[i] != null)
251       {
252         map[1][i] = mp[i][0]-tF;
253       }
254       else
255       {
256         map[1][i] = -1; // indicates an out of range mapping
257       }
258     }
259     return map;
260   }
261   /**
262    * addShift
263    * @param pos start position for shift (in original reference frame)
264    * @param shift length of shift
265    *
266   public void addShift(int pos, int shift)
267   {
268     int sidx = 0;
269     int[] rshift=null;
270     while (sidx<shifts.size() && (rshift=(int[]) shifts.elementAt(sidx))[0]<pos)
271       sidx++;
272     if (sidx==shifts.size())
273       shifts.insertElementAt(new int[] { pos, shift}, sidx);
274     else
275       rshift[1]+=shift;
276   }
277    */
278   /**
279    * shift from pos to To(pos)
280    *
281    * @param pos int
282    * @return int shifted position in To, frameshift in From, direction of mapped symbol in To
283    */
284   public int[] shiftFrom(int pos)
285   {
286     return shift(pos, fromShifts, fromRatio, toShifts, toRatio);
287   }
288
289   /**
290    * inverse of shiftFrom - maps pos in To to a position in From
291    * @param pos (in To)
292    * @return shifted position in From, frameshift in To, direction of mapped symbol in From
293    */
294   public int[] shiftTo(int pos)
295   {
296     return shift(pos, toShifts, toRatio, fromShifts, fromRatio);
297   }
298   /**
299    *
300    * @param fromShifts
301    * @param fromRatio
302    * @param toShifts
303    * @param toRatio
304    * @return
305    */
306   private int[] shift(int pos, Vector fromShifts, int fromRatio,
307       Vector toShifts, int toRatio)
308   {
309     int[] fromCount = countPos(fromShifts, pos);
310     if (fromCount==null)
311     {
312       return null;
313     }
314     int fromRemainder=(fromCount[0]-1) % fromRatio;
315     int toCount = 1+(((fromCount[0]-1) / fromRatio) * toRatio);
316     int[] toPos = countToPos(toShifts, toCount);
317     if (toPos==null)
318     {
319       return null; // throw new Error("Bad Mapping!");
320     }
321     //System.out.println(fromCount[0]+" "+fromCount[1]+" "+toCount);
322     return new int[]
323                    {
324         toPos[0], fromRemainder, toPos[1]};
325   }
326   /**
327    * count how many positions pos is along the series of intervals.
328    * @param intVals
329    * @param pos
330    * @return number of positions or null if pos is not within intervals
331    */
332   private int[] countPos(Vector intVals, int pos)
333   {
334     int count=0,intv[],iv=0,ivSize=intVals.size();
335     while (iv<ivSize)
336     {
337       intv = (int[])intVals.elementAt(iv++);
338       if (intv[0] <= intv[1])
339       {
340         if (pos >= intv[0] && pos <= intv[1])
341         {
342           return new int[]
343                          {
344               count + pos - intv[0] + 1, +1};
345         }
346         else
347         {
348           count+=intv[1]-intv[0]+1;
349         }
350       }
351       else
352       {
353         if (pos >= intv[1] && pos <= intv[0])
354         {
355           return new int[]
356                          {
357               count + intv[0] - pos + 1, -1};
358         }
359         else
360         {
361           count+=intv[0]-intv[1]+1;
362         }
363       }
364     }
365     return null;
366   }
367   /**
368    * count out pos positions into a series of intervals and return the position
369    * @param intVals
370    * @param pos
371    * @return position pos in interval set
372    */
373   private int[] countToPos(Vector intVals, int pos)
374   {
375     int count = 0, diff = 0, iv=0,ivSize=intVals.size(), intv[] =
376     {
377         0, 0};
378     while (iv<ivSize)
379     {
380       intv = (int[])intVals.elementAt(iv++);
381       diff = intv[1]-intv[0];
382       if (diff >= 0)
383       {
384         if (pos <= count + 1 + diff)
385         {
386           return new int[]
387                          {
388               pos - count - 1 + intv[0], +1};
389         }
390         else
391         {
392           count+=1+diff;
393         }
394       }
395       else
396       {
397         if (pos <= count + 1 - diff)
398         {
399           return new int[]
400                          {
401               intv[0] - (pos - count - 1), -1};
402         }
403         else
404         {
405           count+=1-diff;
406         }
407       }
408     }
409     return null;//(diff<0) ? (intv[1]-1) : (intv[0]+1);
410   }
411   /**
412    * find series of intervals mapping from start-end in the From map.
413    * @param start position in to map
414    * @param end position in to map
415    * @return series of ranges in from map
416    */
417   public int[] locateInFrom(int start, int end) {
418     // inefficient implementation
419     int fromStart[] = shiftTo(start);
420     int fromEnd[] = shiftTo(end); // needs to be inclusive of end of symbol position
421     if (fromStart==null || fromEnd==null)
422       return null;
423     int iv[] = getIntervals(fromShifts, fromStart, fromEnd,fromRatio);
424     return iv;
425   }
426
427   /**
428    * find series of intervals mapping from start-end in the to map.
429    * @param start position in from map
430    * @param end position in from map
431    * @return series of ranges in to map
432    */
433   public int[] locateInTo(int start, int end) {
434     // inefficient implementation
435     int toStart[] = shiftFrom(start);
436     int toEnd[] = shiftFrom(end);
437     if (toStart==null || toEnd==null)
438       return null;
439     int iv[] = getIntervals(toShifts, toStart, toEnd, toRatio);
440     return iv;
441   }
442   /**
443    * like shift - except returns the intervals in the given vector of shifts which were spanned
444    * in traversing fromStart to fromEnd
445    * @param fromShifts2
446    * @param fromStart
447    * @param fromEnd
448    * @param fromRatio2
449    * @return
450    */
451   private int[] getIntervals(Vector fromShifts2, int[] fromStart, int[] fromEnd, int fromRatio2)
452   {
453     // TODO: correct for word boundary w.r.t. fromStart->fromEnd direction for startpos and endpos.
454     // test is (1,8,12,17) to (1,5) and features on to : 2,2; 3,3; 4,3; 3,4; 4,4; 5,3; 3,5; 2,4; 4,2;
455     // correct for word direction for start and end :
456     int startpos = fromStart[0]+fromStart[2]*(fromRatio2-1); // Math.min(fromStart[0], .. );
457     int endpos = fromEnd[0]+fromEnd[2]*(fromRatio2-1); // Math.max(fromEnd[0],);
458     int intv=0,intvSize= fromShifts2.size();
459     int iv[],i=0,fs=-1,fe=-1; // containing intervals
460     while (intv<intvSize && (fs==-1 || fe==-1)) {
461       iv = (int[]) fromShifts2.elementAt(intv++);
462       if (iv[0]<=iv[1]) {
463         if (fs==-1 && startpos>=iv[0] && startpos<=iv[1]) {
464           fs = i;
465         }
466         if (fe==-1 && endpos>=iv[0] && endpos<=iv[1]) {
467           fe = i;
468         }
469       } else {
470         if (fs==-1 && startpos<=iv[0] && startpos>=iv[1]) {
471           fs = i;
472         }
473         if (fe==-1 && endpos<=iv[0] && endpos>=iv[1]) {
474           fe = i;
475         }
476       }
477       i++;
478     }
479     if (fs==fe && fe==-1)
480       return null;
481     Vector ranges=new Vector();
482     if (fs<=fe) {
483       intv = fs;
484       i=fs;
485       // truncate initial interval
486       iv = (int[]) fromShifts2.elementAt(intv++);
487       iv = new int[] { iv[0], iv[1]};// clone
488       if (i==fs)
489         iv[0] = startpos;
490       while (i!=fe) {
491         ranges.addElement(iv); // add initial range
492         iv = (int[]) fromShifts2.elementAt(intv++); // get next interval
493         iv = new int[] { iv[0], iv[1]};// clone
494         i++;
495       }
496       if (i==fe)
497         iv[1] = endpos;
498       ranges.addElement(iv); // add only - or final range
499     } else {
500       // walk from end of interval.
501       i=fromShifts2.size()-1;
502       while (i>fs) {
503         i--;
504       }
505       iv = (int[]) fromShifts2.elementAt(i);
506       iv = new int[] { iv[1], iv[0]};//  reverse and clone
507       // truncate initial interval
508       if (i==fs)
509       {
510         iv[0] = startpos;
511       }
512       while (i!=fe) {
513         ranges.addElement(iv); // add (truncated) reversed interval
514         iv = (int[]) fromShifts2.elementAt(--i);
515         iv = new int[] { iv[1], iv[0] }; // reverse and clone
516       }
517       if (i==fe) {
518         // interval is already reversed
519         iv[1] = endpos;
520       }
521       ranges.addElement(iv); // add only - or final range
522     }
523     // create array of start end intervals.
524     int[] range = null;
525     if (ranges!=null && ranges.size()>0)
526     {
527       range = new int[ranges.size()*2];
528       intv = 0;
529       intvSize=ranges.size();
530       i=0;
531       while (intv<intvSize)
532       {
533         iv = (int[]) ranges.elementAt(intv);
534         range[i++] = iv[0];
535         range[i++] = iv[1];
536         ranges.setElementAt(null, intv++); // remove
537       }
538     }
539     return range;
540   }
541   /**
542  * get the 'initial' position of mpos in To
543  * @param mpos position in from
544  * @return position of first word in to reference frame
545  */
546 public int getToPosition(int mpos)
547 {
548   int[] mp = shiftTo(mpos);
549   if (mp!=null)
550   {
551     return mp[0];
552   }
553   return mpos;
554 }
555 /**
556  * get range of positions in To frame for the mpos word in From
557  * @param mpos position in From
558  * @return null or int[] first position in To for mpos, last position in to for Mpos
559  */
560 public int[] getToWord(int mpos) {
561   int[] mp=shiftTo(mpos);
562   if (mp!=null) {
563       return new int[] {mp[0], mp[0]+mp[2]*(getFromRatio()-1)};
564   }
565   return null;
566 }
567 /**
568  * get From position in the associated
569  * reference frame for position pos in the
570  * associated sequence.
571  * @param pos
572  * @return
573  */
574 public int getMappedPosition(int pos) {
575   int[] mp = shiftFrom(pos);
576   if (mp!=null)
577   {
578     return mp[0];
579   }
580   return pos;
581 }
582 public int[] getMappedWord(int pos) {
583   int[] mp = shiftFrom(pos);
584   if (mp!=null)
585   {
586     return new int[] { mp[0], mp[0]+mp[2]*(getToRatio()-1)};
587   }
588   return null;
589 }
590
591   /**
592    * test routine. not incremental.
593    * @param ml
594    * @param fromS
595    * @param fromE
596    */
597   public static void testMap(MapList ml, int fromS, int fromE)
598   {
599     for (int from = 1; from <= 25; from++)
600     {
601       int[] too=ml.shiftFrom(from);
602       System.out.print("ShiftFrom("+from+")==");
603       if (too==null)
604       {
605         System.out.print("NaN\n");
606       }
607       else
608       {
609         System.out.print(too[0]+" % "+too[1]+" ("+too[2]+")");
610         System.out.print("\t+--+\t");
611         int[] toofrom=ml.shiftTo(too[0]);
612         if (toofrom != null)
613         {
614           if (toofrom[0]!=from)
615           {
616             System.err.println("Mapping not reflexive:" + from + " " + too[0] +
617                 "->" + toofrom[0]);
618           }
619           System.out.println("ShiftTo(" + too[0] + ")==" + toofrom[0] + " % " +
620               toofrom[1]+" ("+toofrom[2]+")");
621         }
622         else
623         {
624           System.out.println("ShiftTo(" + too[0] + ")==" +
625           "NaN! - not Bijective Mapping!");
626         }
627       }
628     }
629     int mmap[][] = ml.makeFromMap();
630     System.out.println("FromMap : (" + mmap[0][0] + " " + mmap[0][1] + " " +
631         mmap[0][2] + " " + mmap[0][3] + " ");
632     for (int i = 1; i <= mmap[1].length; i++)
633     {
634       if (mmap[1][i - 1] == -1)
635       {
636         System.out.print(i+"=XXX");
637
638       }
639       else
640       {
641         System.out.print(i+"="+(mmap[0][2]+mmap[1][i-1]));
642       }
643       if (i % 20==0)
644       {
645         System.out.print("\n");
646       }
647       else
648       {
649         System.out.print(",");
650       }
651     }
652     //test range function
653     System.out.print("\nTest locateInFrom\n");
654     {
655       int f=mmap[0][2],t=mmap[0][3];
656       while (f<=t) {
657         System.out.println("Range "+f+" to "+t);
658         int rng[] = ml.locateInFrom(f,t);
659         if (rng!=null)
660         {
661           for (int i=0; i<rng.length; i++) {
662             System.out.print(rng[i]+((i%2==0) ? "," : ";"));
663           }
664         }
665         else
666         {
667           System.out.println("No range!");
668         }
669         System.out.print("\nReversed\n");
670         rng = ml.locateInFrom(t,f);
671         if (rng!=null)
672         {
673           for (int i=0; i<rng.length; i++) {
674             System.out.print(rng[i]+((i%2==0) ? "," : ";"));
675           }
676         }
677         else
678         {
679           System.out.println("No range!");
680         }
681         System.out.print("\n");
682         f++;t--;
683       }
684     }
685     System.out.print("\n");
686     mmap = ml.makeToMap();
687     System.out.println("ToMap : (" + mmap[0][0] + " " + mmap[0][1] + " " +
688         mmap[0][2] + " " + mmap[0][3] + " ");
689     for (int i = 1; i <= mmap[1].length; i++)
690     {
691       if (mmap[1][i - 1] == -1)
692       {
693         System.out.print(i+"=XXX");
694
695       }
696       else
697       {
698         System.out.print(i+"="+(mmap[0][2]+mmap[1][i-1]));
699       }
700       if (i % 20==0)
701       {
702         System.out.print("\n");
703       }
704       else
705       {
706         System.out.print(",");
707       }
708     }
709     System.out.print("\n");
710     //test range function
711     System.out.print("\nTest locateInTo\n");
712     {
713       int f=mmap[0][2],t=mmap[0][3];
714       while (f<=t) {
715         System.out.println("Range "+f+" to "+t);
716         int rng[] = ml.locateInTo(f,t);
717         if (rng!=null) {
718           for (int i=0; i<rng.length; i++) {
719             System.out.print(rng[i]+((i%2==0) ? "," : ";"));
720           }
721         }
722         else
723         {
724           System.out.println("No range!");
725         }
726         System.out.print("\nReversed\n");
727         rng = ml.locateInTo(t,f);
728         if (rng!=null)
729         {
730           for (int i=0; i<rng.length; i++) {
731             System.out.print(rng[i]+((i%2==0) ? "," : ";"));
732           }
733         }
734         else
735         {
736           System.out.println("No range!");
737         }
738         f++; t--;
739         System.out.print("\n");
740       }
741     }
742
743   }
744
745   public static void main(String argv[])
746   {
747     MapList ml = new MapList(new int[]
748                                      {1, 5, 10, 15, 25, 20},
749                                      new int[]
750                                              {51, 1}, 1, 3);
751     MapList ml1 = new MapList(new int[]
752                                       {1, 3, 17, 4},
753                                       new int[]
754                                               {51, 1}, 1, 3);
755     MapList ml2 = new MapList(new int[] { 1, 60 },
756         new int[] { 1, 20 }, 3, 1);
757     // test internal consistency
758     int to[] = new int[51];
759     MapList.testMap(ml, 1, 60);
760     /*
761       for (int from=1; from<=51; from++) {
762           int[] too=ml.shiftTo(from);
763           int[] toofrom=ml.shiftFrom(too[0]);
764           System.out.println("ShiftFrom("+from+")=="+too[0]+" % "+too[1]+"\t+-+\tShiftTo("+too[0]+")=="+toofrom[0]+" % "+toofrom[1]);
765       }*/
766     System.out.print("Success?\n"); // if we get here - something must be working!
767   }
768 }