From a738a37240b13fa22aa7a3333c0a58c71759ab22 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Thu, 5 Oct 2017 15:15:41 +0100 Subject: [PATCH] JAL-2738 method to compound / traverse / convolve two mappings --- src/jalview/util/MapList.java | 59 ++++++++++++++++++ test/jalview/util/MapListTest.java | 118 ++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) diff --git a/src/jalview/util/MapList.java b/src/jalview/util/MapList.java index 4658724..3ce0bb3 100644 --- a/src/jalview/util/MapList.java +++ b/src/jalview/util/MapList.java @@ -1120,4 +1120,63 @@ public class MapList || (fromRatio == 3 && toRatio == 1); } + /** + * Returns a map which is the composite of this one and the input map. That + * is, the output map has the fromRanges of this map, and its toRanges are the + * toRanges of this map as transformed by the input map. + *

+ * Returns null if the mappings cannot be traversed (not all toRanges of this + * map correspond to fromRanges of the input), or if this.toRatio does not + * match map.fromRatio. + * + *

+   * Example 1:
+   *    this:   from [1-100] to [501-600]
+   *    input:  from [10-40] to [60-90]
+   *    output: from [10-40] to [560-590]
+   * Example 2 ('reverse strand exons'):
+   *    this:   from [1-100] to [2000-1951], [1000-951] // transcript to loci
+   *    input:  from [1-50]  to [41-90] // CDS to transcript
+   *    output: from [10-40] to [1960-1951], [1000-971] // CDS to gene loci
+   * 
+ * + * @param map + * @return + */ + public MapList traverse(MapList map) + { + if (map == null) + { + return null; + } + + /* + * compound the ratios by this rule: + * A:B with M:N gives A*M:B*N + * reduced by greatest common divisor + * so 1:3 with 3:3 is 3:9 or 1:3 + * 1:3 with 3:1 is 3:3 or 1:1 + * 1:3 with 1:3 is 1:9 + * 2:5 with 3:7 is 6:35 + */ + int outFromRatio = getFromRatio() * map.getFromRatio(); + int outToRatio = getToRatio() * map.getToRatio(); + int gcd = MathUtils.gcd(outFromRatio, outToRatio); + outFromRatio /= gcd; + outToRatio /= gcd; + + List toRanges = new ArrayList<>(); + for (int[] range : getToRanges()) + { + int[] transferred = map.locateInTo(range[0], range[1]); + if (transferred == null) + { + return null; + } + toRanges.add(transferred); + } + + return new MapList(getFromRanges(), toRanges, outFromRatio, outToRatio); + } + } diff --git a/test/jalview/util/MapListTest.java b/test/jalview/util/MapListTest.java index a2f38e2..f3395ca 100644 --- a/test/jalview/util/MapListTest.java +++ b/test/jalview/util/MapListTest.java @@ -814,4 +814,122 @@ public class MapListTest assertEquals(1, merged.size()); assertArrayEquals(new int[] { 9, 0 }, merged.get(0)); } + + /** + * Test the method that compounds ('traverses') two mappings + */ + @Test + public void testTraverse() + { + /* + * simple 1:1 plus 1:1 forwards + */ + MapList ml1 = new MapList(new int[] { 3, 4, 8, 12 }, new int[] { 5, 8, + 11, 13 }, 1, 1); + MapList ml2 = new MapList(new int[] { 1, 50 }, new int[] { 40, 45, 70, + 75, 90, 127 }, 1, 1); + MapList compound = ml1.traverse(ml2); + + assertEquals(compound.getFromRatio(), 1); + assertEquals(compound.getToRatio(), 1); + List fromRanges = compound.getFromRanges(); + assertEquals(fromRanges.size(), 2); + assertArrayEquals(new int[] { 3, 4 }, fromRanges.get(0)); + assertArrayEquals(new int[] { 8, 12 }, fromRanges.get(1)); + List toRanges = compound.getToRanges(); + assertEquals(toRanges.size(), 2); + // 5-8 maps to 44-45,70-71 + // 11-13 maps to 74-75,90 + assertArrayEquals(new int[] { 44, 45, 70, 71 }, toRanges.get(0)); + assertArrayEquals(new int[] { 74, 75, 90, 90 }, toRanges.get(1)); + + /* + * 1:1 over 1:1 backwards ('reverse strand') + */ + ml1 = new MapList(new int[] { 1, 50 }, new int[] { 70, 119 }, 1, 1); + ml2 = new MapList(new int[] { 1, 500 }, + new int[] { 1000, 901, 600, 201 }, 1, 1); + compound = ml1.traverse(ml2); + + assertEquals(compound.getFromRatio(), 1); + assertEquals(compound.getToRatio(), 1); + fromRanges = compound.getFromRanges(); + assertEquals(fromRanges.size(), 1); + assertArrayEquals(new int[] { 1, 50 }, fromRanges.get(0)); + toRanges = compound.getToRanges(); + assertEquals(toRanges.size(), 1); + assertArrayEquals(new int[] { 931, 901, 600, 582 }, toRanges.get(0)); + + /* + * 1:1 plus 1:3 should result in 1:3 + */ + ml1 = new MapList(new int[] { 1, 30 }, new int[] { 11, 40 }, 1, 1); + ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 50, 91, 340 }, + 1, 3); + compound = ml1.traverse(ml2); + + assertEquals(compound.getFromRatio(), 1); + assertEquals(compound.getToRatio(), 3); + fromRanges = compound.getFromRanges(); + assertEquals(fromRanges.size(), 1); + assertArrayEquals(new int[] { 1, 30 }, fromRanges.get(0)); + // 11-40 maps to 31-50,91-160 + toRanges = compound.getToRanges(); + assertEquals(toRanges.size(), 1); + assertArrayEquals(new int[] { 31, 50, 91, 160 }, toRanges.get(0)); + + /* + * 3:1 plus 1:1 should result in 3:1 + */ + ml1 = new MapList(new int[] { 1, 30 }, new int[] { 11, 20 }, 3, 1); + ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 15, 91, 175 }, + 1, 1); + compound = ml1.traverse(ml2); + + assertEquals(compound.getFromRatio(), 3); + assertEquals(compound.getToRatio(), 1); + fromRanges = compound.getFromRanges(); + assertEquals(fromRanges.size(), 1); + assertArrayEquals(new int[] { 1, 30 }, fromRanges.get(0)); + // 11-20 maps to 11-15, 91-95 + toRanges = compound.getToRanges(); + assertEquals(toRanges.size(), 1); + assertArrayEquals(new int[] { 11, 15, 91, 95 }, toRanges.get(0)); + + /* + * 1:3 plus 3:1 should result in 1:1 + */ + ml1 = new MapList(new int[] { 21, 40 }, new int[] { 13, 72 }, 1, 3); + ml2 = new MapList(new int[] { 1, 300 }, new int[] { 51, 70, 121, 200 }, + 3, 1); + compound = ml1.traverse(ml2); + + assertEquals(compound.getFromRatio(), 1); + assertEquals(compound.getToRatio(), 1); + fromRanges = compound.getFromRanges(); + assertEquals(fromRanges.size(), 1); + assertArrayEquals(new int[] { 21, 40 }, fromRanges.get(0)); + // 13-72 maps 3:1 to 55-70, 121-124 + toRanges = compound.getToRanges(); + assertEquals(toRanges.size(), 1); + assertArrayEquals(new int[] { 55, 70, 121, 124 }, toRanges.get(0)); + + /* + * 3:1 plus 1:3 should result in 1:1 + */ + ml1 = new MapList(new int[] { 31, 90 }, new int[] { 13, 32 }, 3, 1); + ml2 = new MapList(new int[] { 11, 40 }, new int[] { 41, 50, 71, 150 }, + 1, 3); + compound = ml1.traverse(ml2); + + assertEquals(compound.getFromRatio(), 1); + assertEquals(compound.getToRatio(), 1); + fromRanges = compound.getFromRanges(); + assertEquals(fromRanges.size(), 1); + assertArrayEquals(new int[] { 31, 90 }, fromRanges.get(0)); + // 13-32 maps to 47-50,71-126 + toRanges = compound.getToRanges(); + assertEquals(toRanges.size(), 1); + assertArrayEquals(new int[] { 47, 50, 71, 126 }, toRanges.get(0)); + } } -- 1.7.10.2