JAL-3761 debugged and working (but not tidied) locateInFrom2/To2
[jalview.git] / test / jalview / util / MapListTest.java
index 07da78f..fd34902 100644 (file)
@@ -53,7 +53,7 @@ public class MapListTest
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  @Test(groups = { "Functional" })
+  @Test(groups = { "Functional" }, enabled = false)
   public void testSomething()
   {
     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 },
@@ -271,17 +271,24 @@ public class MapListTest
     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
     assertEquals("[1, 6]", Arrays.toString(ml.locateInFrom(1, 2)));
     assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(1, 3)));
+    // reversed range treated as if forwards:
+    assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(3, 1)));
     assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 4)));
     assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
     assertEquals("[4, 12]", Arrays.toString(ml.locateInFrom(2, 4)));
     assertEquals("[7, 12]", Arrays.toString(ml.locateInFrom(3, 4)));
     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
-    // reversing the range reverses the result:
-    assertEquals("[12, 7]", Arrays.toString(ml.locateInFrom(4, 3))); // fails with [10, 9] !
 
+    /*
+     * partial overlap
+     */
+    assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 5)));
+    assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(-1, 1)));
+
+    /*
+     * no overlap
+     */
     assertNull(ml.locateInFrom(0, 0));
-    assertNull(ml.locateInFrom(1, 5));
-    assertNull(ml.locateInFrom(-1, 1));
   }
 
   /**
@@ -314,8 +321,8 @@ public class MapListTest
     assertEquals("[12, 10]", Arrays.toString(ml.locateInFrom(1, 1)));
     assertEquals("[9, 4]", Arrays.toString(ml.locateInFrom(2, 3)));
   }
-  
-   /**
+
+  /**
    * Tests for method that locates ranges in the 'to' map for given range in the
    * 'from' map.
    */
@@ -337,8 +344,8 @@ public class MapListTest
     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 12)));
     assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
     assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(4, 12)));
-    // reversing the 'from' range reverses the result
-    assertEquals("[4, 2]", Arrays.toString(ml.locateInTo(12, 4)));
+    // reverse range treated as if forwards:
+    assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(12, 4)));
 
     /*
      * A part codon is treated as if a whole one.
@@ -350,9 +357,16 @@ public class MapListTest
     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(3, 11)));
     assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(5, 11)));
 
+    /*
+     * partial overlap
+     */
+    assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 13)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(-1, 2)));
+    
+    /*
+     * no overlap
+     */
     assertNull(ml.locateInTo(0, 0));
-    assertNull(ml.locateInTo(1, 13));
-    assertNull(ml.locateInTo(-1, 1));
   }
 
   /**
@@ -374,14 +388,6 @@ public class MapListTest
     MapList ml = new MapList(codons, protein, 3, 1);
 
     /*
-     * Can't map from an unmapped position
-     */
-    assertNull(ml.locateInTo(1, 2));
-    assertNull(ml.locateInTo(1, 4));
-    assertNull(ml.locateInTo(2, 4));
-    assertNull(ml.locateInTo(4, 4));
-
-    /*
      * Valid range or subrange of codon1 maps to protein1
      */
     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 2)));
@@ -395,6 +401,18 @@ public class MapListTest
 
     // codon positions 7 to 17 (part) cover proteins 2/3/4 at positions 3/4/6
     assertEquals("[3, 4, 6, 6]", Arrays.toString(ml.locateInTo(7, 17)));
+
+    /*
+     * partial overlap
+     */
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 2)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 4)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 4)));
+    
+    /*
+     * no overlap
+     */
+    assertNull(ml.locateInTo(4, 4));
   }
 
   /**
@@ -941,10 +959,10 @@ public class MapListTest
     assertArrayEquals(new int[] { 71, 126 }, toRanges.get(1));
 
     /*
-     * method returns null if not all regions are mapped through
+     * if not all regions are mapped through, returns what is
      */
     ml1 = new MapList(new int[] { 1, 50 }, new int[] { 101, 150 }, 1, 1);
-    ml2 = new MapList(new int[] { 131, 180 }, new int[] { 201, 250 }, 1, 3);
+    ml2 = new MapList(new int[] { 131, 180 }, new int[] { 201, 250 }, 1, 1);
     compound = ml1.traverse(ml2);
     assertNull(compound);
   }
@@ -1000,19 +1018,29 @@ public class MapListTest
     assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
     assertEquals("[7, 12, 12, 17]", Arrays.toString(ml.locateInFrom(3, 6)));
 
+    /*
+     * partial overlap of range
+     */
+    assertEquals("[4, 12, 12, 17]", Arrays.toString(ml.locateInFrom(2, 7)));
+    assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(-1, 1)));
+
+    /*
+     * no overlap in range
+     */
     assertNull(ml.locateInFrom(0, 0));
-    assertNull(ml.locateInFrom(1, 7));
-    assertNull(ml.locateInFrom(-1, 1));
 
     /*
      * gene to CDS...from EMBL:MN908947
      */
-    int [] gene = new int[] { 266, 13468, 13468, 21555 };
-    int [] cds = new int[] { 1, 21291 };
+    int[] gene = new int[] { 266, 13468, 13468, 21555 };
+    int[] cds = new int[] { 1, 21291 };
     ml = new MapList(gene, cds, 1, 1);
-    assertEquals("[13468, 13468]", Arrays.toString(ml.locateInFrom(13203, 13203)));
-    assertEquals("[13468, 13468]", Arrays.toString(ml.locateInFrom(13204, 13204)));
-    assertEquals("[13468, 13468]", Arrays.toString(ml.locateInFrom(13203, 13204)));
+    assertEquals("[13468, 13468]",
+            Arrays.toString(ml.locateInFrom(13203, 13203)));
+    assertEquals("[13468, 13468]",
+            Arrays.toString(ml.locateInFrom(13204, 13204)));
+    assertEquals("[13468, 13468, 13468, 13468]",
+            Arrays.toString(ml.locateInFrom(13203, 13204)));
   }
 
   /**
@@ -1038,17 +1066,36 @@ public class MapListTest
     assertEquals("[4, 6]", Arrays.toString(ml.locateInTo(11, 15)));
     assertEquals("[6, 6]", Arrays.toString(ml.locateInTo(15, 17)));
 
+    /*
+     * no overlap
+     */
     assertNull(ml.locateInTo(0, 0));
-    assertNull(ml.locateInTo(1, 18));
-    assertNull(ml.locateInTo(-1, 1));
+    
+    /*
+     * partial overlap
+     */
+    assertEquals("[1, 6]", Arrays.toString(ml.locateInTo(1, 18)));
+    assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(-1, 1)));
 
     /*
      * gene to CDS...from EMBL:MN908947
+     * the base at 13468 is used twice in transcription
      */
-    int [] gene = new int[] { 266, 13468, 13468, 21555 };
-    int [] cds = new int[] { 1, 21291 };
+    int[] gene = new int[] { 266, 13468, 13468, 21555 };
+    int[] cds = new int[] { 1, 21291 };
     ml = new MapList(gene, cds, 1, 1);
-    assertEquals("[13203, 13204]", Arrays.toString(ml.locateInTo(13468, 13468)));
+    assertEquals("[13203, 13204]",
+            Arrays.toString(ml.locateInTo(13468, 13468)));
+    
+    /*
+     * gene to protein
+     * the base at 13468 is in the codon for 4401N and also 4402R
+     */
+    gene = new int[] { 266, 13468, 13468, 21552 }; // stop codon excluded
+    protein = new int[] { 1, 7096 };
+    ml = new MapList(gene, protein, 3, 1);
+    assertEquals("[4401, 4402]",
+            Arrays.toString(ml.locateInTo(13468, 13468)));
   }
 
   @Test(groups = { "Functional" })
@@ -1074,121 +1121,253 @@ public class MapListTest
     }
 
     List<int[]> intervals = new ArrayList<>();
-    assertNull(MapList.countPositions(intervals,  1));
-    
+    assertNull(MapList.countPositions(intervals, 1));
+
     /*
      * forward strand
      */
-    intervals.add(new int[] {10, 20});
-    assertNull(MapList.countPositions(intervals,  9));
-    assertNull(MapList.countPositions(intervals,  21));
-    assertArrayEquals(new int[] {1, 1}, MapList.countPositions(intervals,  10));
-    assertArrayEquals(new int[] {6, 1}, MapList.countPositions(intervals,  15));
-    assertArrayEquals(new int[] {11, 1}, MapList.countPositions(intervals,  20));
-
-    intervals.add(new int[] {25, 25});
-    assertArrayEquals(new int[] {12, 1}, MapList.countPositions(intervals,  25));
-
-    // next interval repeats position 25 - which should be counted twice if traversed
-    intervals.add(new int[] {25, 26});
-    assertArrayEquals(new int[] {12, 1}, MapList.countPositions(intervals,  25));
-    assertArrayEquals(new int[] {14, 1}, MapList.countPositions(intervals,  26));
-    
+    intervals.add(new int[] { 10, 20 });
+    assertNull(MapList.countPositions(intervals, 9));
+    assertNull(MapList.countPositions(intervals, 21));
+    assertArrayEquals(new int[] { 1, 1 },
+            MapList.countPositions(intervals, 10));
+    assertArrayEquals(new int[] { 6, 1 },
+            MapList.countPositions(intervals, 15));
+    assertArrayEquals(new int[] { 11, 1 },
+            MapList.countPositions(intervals, 20));
+
+    intervals.add(new int[] { 25, 25 });
+    assertArrayEquals(new int[] { 12, 1 },
+            MapList.countPositions(intervals, 25));
+
+    // next interval repeats position 25 - which should be counted twice if
+    // traversed
+    intervals.add(new int[] { 25, 26 });
+    assertArrayEquals(new int[] { 12, 1 },
+            MapList.countPositions(intervals, 25));
+    assertArrayEquals(new int[] { 14, 1 },
+            MapList.countPositions(intervals, 26));
+
     /*
      * reverse strand
      */
     intervals.clear();
-    intervals.add(new int[] {5, -5});
-    assertNull(MapList.countPositions(intervals,  6));
-    assertNull(MapList.countPositions(intervals,  -6));
-    assertArrayEquals(new int[] {1, -1}, MapList.countPositions(intervals,  5));
-    assertArrayEquals(new int[] {7, -1}, MapList.countPositions(intervals,  -1));
-    assertArrayEquals(new int[] {11, -1}, MapList.countPositions(intervals,  -5));
-    
+    intervals.add(new int[] { 5, -5 });
+    assertNull(MapList.countPositions(intervals, 6));
+    assertNull(MapList.countPositions(intervals, -6));
+    assertArrayEquals(new int[] { 1, -1 },
+            MapList.countPositions(intervals, 5));
+    assertArrayEquals(new int[] { 7, -1 },
+            MapList.countPositions(intervals, -1));
+    assertArrayEquals(new int[] { 11, -1 },
+            MapList.countPositions(intervals, -5));
+
     /*
      * reverse then forward
      */
-    intervals.add(new int[] {5, 10});
-    assertArrayEquals(new int[] {13, 1}, MapList.countPositions(intervals,  6));
-    
+    intervals.add(new int[] { 5, 10 });
+    assertArrayEquals(new int[] { 13, 1 },
+            MapList.countPositions(intervals, 6));
+
     /*
      * reverse then forward then reverse
      */
-    intervals.add(new int[] {-10, -20});
-    assertArrayEquals(new int[] {20, -1}, MapList.countPositions(intervals,  -12));
-    
+    intervals.add(new int[] { -10, -20 });
+    assertArrayEquals(new int[] { 20, -1 },
+            MapList.countPositions(intervals, -12));
+
     /*
      * an interval [x, x] is treated as forward
      */
-    intervals.add(new int[] {30, 30});
-    assertArrayEquals(new int[] {29, 1}, MapList.countPositions(intervals,  30));
-    
+    intervals.add(new int[] { 30, 30 });
+    assertArrayEquals(new int[] { 29, 1 },
+            MapList.countPositions(intervals, 30));
+
     /*
      * it is the first matched occurrence that is returned
      */
     intervals.clear();
-    intervals.add(new int[] {1, 2});
-    intervals.add(new int[] {2, 3});
-    assertArrayEquals(new int[] {2, 1}, MapList.countPositions(intervals,  2));
-    intervals.add(new int[] {-1, -2});
-    intervals.add(new int[] {-2, -3});
-    assertArrayEquals(new int[] {6, -1}, MapList.countPositions(intervals,  -2));
+    intervals.add(new int[] { 1, 2 });
+    intervals.add(new int[] { 2, 3 });
+    assertArrayEquals(new int[] { 2, 1 },
+            MapList.countPositions(intervals, 2));
+    intervals.add(new int[] { -1, -2 });
+    intervals.add(new int[] { -2, -3 });
+    assertArrayEquals(new int[] { 6, -1 },
+            MapList.countPositions(intervals, -2));
+  }
+
+  /**
+   * Tests for helper method that adds any overlap (plus offset) to a list of
+   * overlaps
+   */
+  @Test(groups = { "Functional" })
+  public void testAddOverlap()
+  {
+    List<int[]> overlaps = new ArrayList<>();
+    int[] candidate = new int[] { 10, 19 };
+    MapList.addOverlap(overlaps, 5, candidate, 20, 30); // doesn't overlap
+    assertTrue(overlaps.isEmpty());
+    MapList.addOverlap(overlaps, 5, candidate, 31, 40); // doesn't overlap
+    assertTrue(overlaps.isEmpty());
+
+    /*
+     * 10-19 overlaps 15-25 at 15-19, which is offset 5-9 in 10-19
+     * + 5 initial offset
+     */
+    MapList.addOverlap(overlaps, 5, candidate, 15, 25);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 10, 14 }, overlaps.get(0));
+
+    /*
+     * reverse range overlap:
+     * 300-20 overlaps 15-25 at 25-20, which is offset 275-280 in 300-20
+     * + 8 initial offset
+     */
+    overlaps.clear();
+    candidate = new int[] { 300, 20 };
+    MapList.addOverlap(overlaps, 8, candidate, 15, 25);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 283, 288 }, overlaps.get(0));
   }
 
   @Test(groups = { "Functional" })
   public void testFindOverlapPositions()
   {
     List<int[]> ranges = new ArrayList<>();
-    List<int[]> overlaps = MapList.findOverlapPositions(ranges,  20,  30);
+    List<int[]> overlaps = MapList.findOverlapPositions(ranges, 20, 30);
+    assertTrue(overlaps.isEmpty()); // nothing to overlap
+
+    ranges.add(new int[] { 15, 25 });
+    overlaps = MapList.findOverlapPositions(ranges, 5, 10);
+    assertTrue(overlaps.isEmpty()); // no overlap
+
+    overlaps = MapList.findOverlapPositions(ranges, 20, 20);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 5, 5 }, overlaps.get(0));
+
+    overlaps = MapList.findOverlapPositions(ranges, 5, 19);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 0, 4 }, overlaps.get(0));
+
+    ranges.add(new int[] { 35, 45 });
+    overlaps = MapList.findOverlapPositions(ranges, 26, 34);
     assertTrue(overlaps.isEmpty());
-    ranges.add(new int[] {15, 25});
-    overlaps = MapList.findOverlapPositions(ranges,  20,  30);
+
+    /*
+     * 24-37 overlaps the end of 15-25 and the start of 35-45
+     * - offset positions are contiguous in the map so merged
+     */
+    overlaps = MapList.findOverlapPositions(ranges, 24, 37);
     assertEquals(1, overlaps.size());
-    assertArrayEquals(new int[] {6, 11}, overlaps.get(0));
+    assertArrayEquals(new int[] { 9, 13 }, overlaps.get(0));
+
+    /*
+     * EMBL:MN908947  https://www.ebi.ac.uk/ena/browser/api/embl/MN908947 
+     * (Covid-SARS-2) CDS mapping with 'slippage'
+     * (base 13468 is used twice in transcription)
+     */
+    ranges.clear();
+    ranges.add(new int[] { 266, 13468 });
+    ranges.add(new int[] { 13468, 21555 });
+
+    // 13468 occupies two offsets in the range list
+    overlaps = MapList.findOverlapPositions(ranges, 13468, 13468);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 13202, 13203 }, overlaps.get(0));
+    overlaps = MapList.findOverlapPositions(ranges, 13469, 13470);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 13204, 13205 }, overlaps.get(0));
+
+    /*
+     * EMBL:J03321 https://www.ebi.ac.uk/ena/browser/api/embl/J03321
+     * circular dna: CDS at [7022-7502, 1-437] 
+     * = 481 + 437 = 918 bases = 305 aa's + stop codon
+     */
+    ranges.clear();
+    ranges.add(new int[] { 7022, 7502 });
+    ranges.add(new int[] { 1, 437 });
+
+    overlaps = MapList.findOverlapPositions(ranges, 438, 7021);
+    assertTrue(overlaps.isEmpty());
+
+    // overlap first exon only:
+    overlaps = MapList.findOverlapPositions(ranges, 7000, 7100);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 0, 78 }, overlaps.get(0));
+
+    // overlap second exon only: offset to mapping includes first exon
+    overlaps = MapList.findOverlapPositions(ranges, 400, 500);
+    assertEquals(1, overlaps.size());
+    assertArrayEquals(new int[] { 880, 917 }, overlaps.get(0));
+
+    // overlap both exons: first exon overlap precedes second exon overlap
+    // offsets of overlaps are not contiguous
+    overlaps = MapList.findOverlapPositions(ranges, 200, 7500);
+    assertEquals(2, overlaps.size());
+    // first overlap is offsets of 7022-7500 in exon 1 (7022-7502):
+    assertArrayEquals(new int[] { 0, 478 }, overlaps.get(0));
+    // second overlap offsets is (first exon length = 481) + (200-437)
+    assertArrayEquals(new int[] { 680, 917 }, overlaps.get(1));
   }
 
   @Test(groups = { "Functional" })
   public void testMapWords()
   {
     List<int[]> ranges = new ArrayList<>();
-    
+
     /*
      * 1:1 (trivial) case
      */
-    ranges.add(new int[] {2, 4});
-    ranges.add(new int[] {6, 9});
-    MapList.mapWords(ranges,  1, 1);
+    ranges.add(new int[] { 2, 4 });
+    ranges.add(new int[] { 6, 9 });
+    MapList.mapWords(ranges, 1, 1);
     assertEquals(ranges.size(), 2);
-    assertArrayEquals(new int[] {2, 4}, ranges.get(0));
-    assertArrayEquals(new int[] {6, 9}, ranges.get(1));
-    
+    assertArrayEquals(new int[] { 2, 4 }, ranges.get(0));
+    assertArrayEquals(new int[] { 6, 9 }, ranges.get(1));
+
     /*
      * 1:3 case (peptide to codon ranges)
      */
-    MapList.mapWords(ranges,  1, 3);
+    MapList.mapWords(ranges, 1, 3);
     assertEquals(ranges.size(), 2);
-    assertArrayEquals(new int[] {6, 14}, ranges.get(0));
-    assertArrayEquals(new int[] {18, 29}, ranges.get(1));
-    
+    assertArrayEquals(new int[] { 6, 14 }, ranges.get(0));
+    assertArrayEquals(new int[] { 18, 29 }, ranges.get(1));
+
     /*
      * 3:1 case (codon or part codon to peptide)
      */
     ranges.clear();
-    ranges.add(new int[] {0, 5}); // 2 whole codons
-    ranges.add(new int[] {7, 11}); // part + whole codon
-    ranges.add(new int[] {15, 19}); // whole + part codon
-    ranges.add(new int[] {23, 27}); // part + part codon
-    ranges.add(new int[] {30, 30}); // first base of codon
-    ranges.add(new int[] {31, 31}); // second base of codon
-    ranges.add(new int[] {32, 32}); // third base of codon
-    MapList.mapWords(ranges,  3, 1);
+    ranges.add(new int[] { 0, 5 }); // 2 whole codons
+    ranges.add(new int[] { 7, 11 }); // part + whole codon
+    ranges.add(new int[] { 15, 19 }); // whole + part codon
+    ranges.add(new int[] { 23, 27 }); // part + part codon
+    ranges.add(new int[] { 30, 30 }); // first base of codon
+    ranges.add(new int[] { 31, 31 }); // second base of codon
+    ranges.add(new int[] { 32, 32 }); // third base of codon
+    MapList.mapWords(ranges, 3, 1);
     assertEquals(ranges.size(), 7);
-    assertArrayEquals(new int[] {0, 1}, ranges.get(0));
-    assertArrayEquals(new int[] {2, 3}, ranges.get(1));
-    assertArrayEquals(new int[] {5, 6}, ranges.get(2));
-    assertArrayEquals(new int[] {7, 9}, ranges.get(3));
-    assertArrayEquals(new int[] {10, 10}, ranges.get(4));
-    assertArrayEquals(new int[] {10, 10}, ranges.get(5));
-    assertArrayEquals(new int[] {10, 10}, ranges.get(6));
+    assertArrayEquals(new int[] { 0, 1 }, ranges.get(0));
+    assertArrayEquals(new int[] { 2, 3 }, ranges.get(1));
+    assertArrayEquals(new int[] { 5, 6 }, ranges.get(2));
+    assertArrayEquals(new int[] { 7, 9 }, ranges.get(3));
+    assertArrayEquals(new int[] { 10, 10 }, ranges.get(4));
+    assertArrayEquals(new int[] { 10, 10 }, ranges.get(5));
+    assertArrayEquals(new int[] { 10, 10 }, ranges.get(6));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testLocateInFrom2()
+  {
+    /*
+     * codons at 11-16, 21-26, 31-36 mapped to peptide positions 1, 3-4, 6-8
+     */
+    MapList ml = new MapList(new int[] { 11, 16, 21, 26, 31, 36 },
+            new int[]
+            { 1, 1, 3, 4, 6, 8 }, 3, 1);
+    assertArrayEquals(new int[] { 11, 13 }, ml.locateInFrom2(1, 1));
+    assertArrayEquals(new int[] { 11, 16 }, ml.locateInFrom2(1, 3));
+    assertArrayEquals(new int[] { 11, 16, 21, 23 }, ml.locateInFrom2(1, 4));
+    assertArrayEquals(new int[] { 14, 16, 21, 23 }, ml.locateInFrom2(3, 4));
   }
 }