JAL-2349 allow PAE or other contact matrices to hold a coordinate mapping allowing...
authorJames Procter <j.procter@dundee.ac.uk>
Fri, 12 May 2023 15:28:31 +0000 (16:28 +0100)
committerJames Procter <j.procter@dundee.ac.uk>
Fri, 12 May 2023 15:28:31 +0000 (16:28 +0100)
16 files changed:
src/jalview/analysis/AverageDistanceEngine.java
src/jalview/api/AlignViewportI.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/ContactListI.java
src/jalview/datamodel/ContactListImpl.java
src/jalview/datamodel/ContactListProviderI.java
src/jalview/datamodel/ContactMapHolder.java
src/jalview/datamodel/ContactMatrix.java
src/jalview/datamodel/ContactMatrixI.java
src/jalview/datamodel/SeqDistanceContactMatrix.java
src/jalview/renderer/ContactGeometry.java
src/jalview/renderer/ContactMapRenderer.java
src/jalview/ws/datamodel/MappableContactMatrixI.java [new file with mode: 0644]
src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java
src/mc_view/PDBChain.java
test/jalview/datamodel/PAEContactMatrixTest.java [new file with mode: 0644]

index e6a763b..c52c7be 100644 (file)
@@ -105,6 +105,10 @@ public class AverageDistanceEngine extends TreeEngine
       {
         distances.setValue(i, i, 0);
         ContactListI jth = cm.getContactList(j);
+        if (jth==null)
+        {
+          break;
+        }
         double prd = 0;
         for (int indx = 0; indx < cm.getHeight(); indx++)
         {
index 03efec5..e3478ea 100644 (file)
@@ -477,6 +477,11 @@ public interface AlignViewportI extends ViewStyleI
    */
   SearchResultsI getSearchResults();
 
+  /**
+   * Retrieve a ContactListI corresponding to column in an annotation row in an alignment.
+   * @param _aa - annotation with associated matrix data
+   * @param column - column in alignment where _aa is associated
+   */
   ContactListI getContactList(AlignmentAnnotation _aa, int column);
 
   /**
index 517a6dd..1ec702c 100755 (executable)
@@ -2076,14 +2076,19 @@ public class Alignment implements AlignmentI, AutoCloseable
     }
     if (cl == null && _aa.sequenceRef != null)
     {
-      int spos = _aa.sequenceRef.findPosition(column);
-      if (spos >= _aa.sequenceRef.getStart()
-              && spos <= 1 + _aa.sequenceRef.getEnd())
+      if (_aa.annotations[column] != null)
       {
-        cl = _aa.sequenceRef.getContactListFor(_aa, spos-_aa.sequenceRef.getStart());
+        // sequence associated
+        cl = _aa.sequenceRef.getContactListFor(_aa, column);
         if (cl == null && _aa.sequenceRef.getDatasetSequence() != null)
         {
-          _aa.sequenceRef.getDatasetSequence().getContactListFor(_aa, spos-_aa.sequenceRef.getStart());
+          int spos = _aa.sequenceRef.findPosition(column);
+          if (spos >= _aa.sequenceRef.getStart()
+                  && spos <= 1 + _aa.sequenceRef.getEnd())
+          {
+            cl = _aa.sequenceRef.getDatasetSequence().getContactListFor(_aa,
+                    spos-_aa.sequenceRef.getStart());
+          }
         }
       }
     }
index ac06adb..d0544b8 100644 (file)
@@ -1,5 +1,9 @@
 package jalview.datamodel;
 
+import java.awt.Color;
+
+import jalview.renderer.ContactGeometry.contactInterval;
+
 public interface ContactListI extends ContactListProviderI
 {
 
@@ -12,4 +16,7 @@ public interface ContactListI extends ContactListProviderI
    */
   ContactRange getRangeFor(int from_column, int to_column);
 
+  default Color getColourForGroup() {
+    return null;
+  }
 }
index 8e806e4..6a37864 100644 (file)
@@ -1,5 +1,7 @@
 package jalview.datamodel;
 
+import jalview.renderer.ContactGeometry.contactInterval;
+
 /**
  * helper class to compute min/max/mean for a range on a contact list
  * 
@@ -94,5 +96,4 @@ public class ContactListImpl implements ContactListI
     }
     return cr;
   }
-
 }
index f027e01..a4e0b79 100644 (file)
@@ -1,5 +1,7 @@
 package jalview.datamodel;
 
+import jalview.renderer.ContactGeometry.contactInterval;
+
 public interface ContactListProviderI
 {
 
@@ -26,4 +28,15 @@ public interface ContactListProviderI
    */
   double getContactAt(int column);
 
+
+  /**
+   * Return positions in local reference corresponding to cStart and cEnd in matrix data
+   * @param cStart
+   * @param cEnd
+   * @return int[] { start, end (inclusive) for each contiguous segment}
+   */
+  default int[] getMappedPositionsFor(int cStart, int cEnd) {
+    return new int[] { cStart, cEnd};
+  }
+
 }
index 296feaf..d80a719 100644 (file)
@@ -5,6 +5,8 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import jalview.ws.datamodel.MappableContactMatrixI;
+
 public class ContactMapHolder implements ContactMapHolderI
 {
 
@@ -28,6 +30,13 @@ public class ContactMapHolder implements ContactMapHolderI
     {
       return null;
     }
+    if (cm instanceof MappableContactMatrixI)
+    {
+      if (_aa.sequenceRef!=null)
+      {
+        return ((MappableContactMatrixI)cm).getMappableContactList(_aa.sequenceRef, column);
+      }
+    }
     // TODO: could resolve sequence position to column position here
     // TODO: what about for complexes - where contactMatrix may involve two or
     // more sequences
@@ -49,9 +58,9 @@ public class ContactMapHolder implements ContactMapHolderI
     contactmaps.put(aa.annotationId, cm);
     // TODO: contact matrices could be intra or inter - more than one refseq
     // possible!
-    if (cm.hasReferenceSeq())
+    if (cm instanceof MappableContactMatrixI)
     {
-      aa.setSequenceRef(cm.getReferenceSeq());
+      aa.setSequenceRef(((MappableContactMatrixI) cm).getReferenceSeq());
     }
     return aa;
   }
index 1b1889e..65fd01c 100644 (file)
@@ -150,20 +150,6 @@ public abstract class ContactMatrix implements ContactMatrixI
   }
 
   @Override
-  public boolean hasReferenceSeq()
-  {
-    // TODO Auto-generated method stub
-    return false;
-  }
-
-  @Override
-  public SequenceI getReferenceSeq()
-  {
-    // TODO Auto-generated method stub
-    return null;
-  }
-
-  @Override
   public String getAnnotLabel()
   {
     return "Contact Matrix";
index 1c169ef..ba2ee48 100644 (file)
@@ -14,10 +14,6 @@ public interface ContactMatrixI
 
   float getMax();
 
-  boolean hasReferenceSeq();
-
-  SequenceI getReferenceSeq();
-
   String getAnnotDescr();
 
   String getAnnotLabel();
@@ -72,5 +68,6 @@ public interface ContactMatrixI
 
   void setColorForGroup(BitSet bs, Color color);
 
-  default Color getColourForGroup(BitSet bs) { return Color.white;};
+  default Color getColourForGroup(BitSet bs) { return Color.white;}
+
 }
index c3f3670..f6377b1 100644 (file)
@@ -5,13 +5,16 @@ import java.util.BitSet;
 import java.util.HashMap;
 import java.util.List;
 
+import jalview.util.MapList;
+import jalview.ws.datamodel.alphafold.MappableContactMatrix;
+
 /**
  * Dummy contact matrix based on sequence distance
  * 
  * @author jprocter
  *
  */
-public class SeqDistanceContactMatrix implements ContactMatrixI
+public class SeqDistanceContactMatrix extends MappableContactMatrix<SeqDistanceContactMatrix> implements ContactMatrixI
 {
   private static final String SEQUENCE_DISTANCE = "SEQUENCE_DISTANCE";
   private int width = 0;
@@ -79,21 +82,6 @@ public class SeqDistanceContactMatrix implements ContactMatrixI
       }
     });
   }
-
-  @Override
-  public boolean hasReferenceSeq()
-  {
-    // TODO Auto-generated method stub
-    return false;
-  }
-
-  @Override
-  public SequenceI getReferenceSeq()
-  {
-    // TODO Auto-generated method stub
-    return null;
-  }
-
   @Override
   public String getAnnotDescr()
   {
@@ -159,4 +147,17 @@ public class SeqDistanceContactMatrix implements ContactMatrixI
   {
     colorMap.put(bs,color);
   }
+  @Override
+  protected double getElementAt(int _column, int i)
+  {
+    return Math.abs(_column-i);
+  }
+
+  @Override
+  protected SeqDistanceContactMatrix newMappableContactMatrix(
+          SequenceI newRefSeq, MapList newFromMapList)
+  {
+    
+    return new SeqDistanceContactMatrix(width);
+  }
 }
index 4aef1d8..2eb325c 100644 (file)
@@ -2,10 +2,23 @@ package jalview.renderer;
 
 import java.util.Iterator;
 
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ContactListI;
-
+import jalview.datamodel.HiddenColumns;
+import jalview.renderer.ContactGeometry.contactInterval;
+
+/**
+ * encapsulate logic for mapping between positions in a ContactList and their
+ * rendered representation in a given number of pixels.
+ * 
+ * @author jprocter
+ *
+ */
 public class ContactGeometry
 {
+  
+  final ContactListI contacts;
+  
   final int pixels_step;
 
   final double contacts_per_pixel;
@@ -14,8 +27,9 @@ public class ContactGeometry
 
   final int graphHeight;
 
-  public ContactGeometry(ContactListI contacts, int graphHeight)
+  public ContactGeometry(final ContactListI contacts, int graphHeight)
   {
+    this.contacts=contacts;
     this.graphHeight = graphHeight;
     contact_height = contacts.getContactHeight();
     // fractional number of contacts covering each pixel
@@ -54,6 +68,47 @@ public class ContactGeometry
     public final int pStart;
 
     public final int pEnd;
+
+  }
+  /**
+   * 
+   * @param columnSelection
+   * @param ci
+   * @param visibleOnly - when true, only test intersection of visible columns given matrix range 
+   * @return true if the range on the matrix specified by ci intersects with selected columns in the ContactListI's reference frame.
+   */
+  
+  boolean intersects(contactInterval ci,ColumnSelection columnSelection, HiddenColumns hiddenColumns,  boolean visibleOnly) {
+    boolean rowsel = false;
+    final int[] mappedRange = contacts.getMappedPositionsFor(ci.cStart, ci.cEnd);
+    if (mappedRange==null)
+    {
+      return false;
+    }
+    boolean containsHidden=false;
+    if (visibleOnly && hiddenColumns!=null && hiddenColumns.hasHiddenColumns())
+    {
+      // TODO: turn into function on hiddenColumns and create test !!
+      Iterator<int[]> viscont = hiddenColumns
+              .getVisContigsIterator(mappedRange[0], mappedRange[1], false);
+      containsHidden = !viscont.hasNext();
+      if (!containsHidden)
+      {
+        for (int[] interval=viscont.next();viscont.hasNext();
+        rowsel |= columnSelection.intersects(interval[0],interval[1]))
+          ;
+      }
+    }
+    else
+    {
+      // if containsHidden is true mappedRange is not visible
+      if (containsHidden)
+      {
+        rowsel = columnSelection.intersects(mappedRange[0], mappedRange[1]);
+      }
+    }
+    return rowsel;
+
   }
 
   /**
index e54f471..00a6169 100644 (file)
@@ -146,19 +146,25 @@ public abstract class ContactMapRenderer implements AnnotationRowRendererI
         x++;
         continue;
       }
-      Color gpcol = (cm==null) ? Color.white: cm.getColourForGroup(cm.getGroupsFor(column));
+      // ContactListI from viewport can map column -> group
+      Color gpcol = (cm==null) ? Color.white: contacts.getColourForGroup(); // cm.getColourForGroup(cm.getGroupsFor(column));
+      
       // feature still in development - highlight or omit regions hidden in
       // the alignment - currently marks them as red rows
       boolean maskHiddenCols = false;
-      // TODO: pass visible column mask to the ContactGeometry object so it maps
+      // TODO: optionally pass visible column mask to the ContactGeometry object so it maps
       // only visible contacts to geometry
       // Bean holding mapping from contact list to pixels
+      // TODO: allow bracketing/limiting of range on contacts to render (like visible column mask but more flexible?)
+      
+      // COntactListI provides mapping for column -> cm-groupmapping
       final ContactGeometry cgeom = new ContactGeometry(contacts,
               _aa.graphHeight);
 
       for (int ht = y2, eht = y2
               - _aa.graphHeight; ht >= eht; ht -= cgeom.pixels_step)
       {
+        
         ContactGeometry.contactInterval ci = cgeom.mapFor(y2 - ht,
                 y2 - ht + cgeom.pixels_step);
         // cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel);
@@ -169,29 +175,7 @@ public abstract class ContactMapRenderer implements AnnotationRowRendererI
         boolean rowsel = false, containsHidden = false;
         if (columnSelection != null)
         {
-          if (_aa.sequenceRef == null)
-          {
-            rowsel = columnSelection.intersects(ci.cStart, ci.cEnd);
-          }
-          else
-          {
-            // TODO check we have correctly mapped cstart to local sequence
-            // numbering
-            int s = _aa.sequenceRef.findIndex(ci.cStart);
-            int e = _aa.sequenceRef.findIndex(ci.cEnd);
-            if (maskHiddenCols && hasHiddenColumns)
-            {
-              // TODO: turn into function and create test !!
-              Iterator<int[]> viscont = hiddenColumns
-                      .getVisContigsIterator(s, e, false);
-              containsHidden = !viscont.hasNext();
-            }
-            if (s > 0 && s < _aa.sequenceRef.getLength())
-            {
-              rowsel = columnSelection.intersects(s, e);
-            }
-
-          }
+          rowsel = cgeom.intersects(ci, columnSelection, hiddenColumns, maskHiddenCols);
         }
         // TODO: show selected region
         if (colsel || rowsel)
diff --git a/src/jalview/ws/datamodel/MappableContactMatrixI.java b/src/jalview/ws/datamodel/MappableContactMatrixI.java
new file mode 100644 (file)
index 0000000..4abaa5d
--- /dev/null
@@ -0,0 +1,31 @@
+package jalview.ws.datamodel;
+
+import jalview.datamodel.ContactListI;
+import jalview.datamodel.ContactMatrixI;
+import jalview.datamodel.Mapping;
+import jalview.datamodel.SequenceI;
+
+public interface MappableContactMatrixI extends ContactMatrixI
+{
+
+  boolean hasReferenceSeq();
+
+  SequenceI getReferenceSeq();
+  /**
+   * remaps the matrix to a new reference sequence
+   * @param dsq 
+   * @param sqmpping - mapping from current reference to new reference - 1:1 only
+   * @return new ContactMatrixI instance with updated mapping
+   */
+  MappableContactMatrixI liftOver(SequenceI dsq, Mapping sqmpping);
+  
+  /**
+   * like ContactMatrixI.getContactList(int column) but
+   * @param localFrame - sequence or other object that this contact matrix is associated with
+   * @param column - position in localFrame
+   * @return ContactListI that returns contacts w.r.t. localFrame
+   */
+  
+  ContactListI getMappableContactList(SequenceI localFrame, int column);
+
+}
index 22dd7fb..9d7891d 100644 (file)
@@ -11,51 +11,36 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import org.json.simple.JSONObject;
 
 import jalview.analysis.AverageDistanceEngine;
 import jalview.bin.Console;
+import jalview.datamodel.Annotation;
 import jalview.datamodel.BinaryNode;
 import jalview.datamodel.ContactListI;
 import jalview.datamodel.ContactListImpl;
 import jalview.datamodel.ContactListProviderI;
 import jalview.datamodel.ContactMatrixI;
+import jalview.datamodel.Mapping;
 import jalview.datamodel.SequenceDummy;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormatException;
 import jalview.io.FileParse;
+import jalview.util.MapList;
 import jalview.util.MapUtils;
 import jalview.ws.dbsources.EBIAlfaFold;
 
-public class PAEContactMatrix implements ContactMatrixI
+public class PAEContactMatrix extends MappableContactMatrix<PAEContactMatrix> implements ContactMatrixI
 {
-
-  SequenceI refSeq = null;
-
-  /**
-   * the length that refSeq is expected to be (excluding gaps, of course)
-   */
-  int length;
-
   int maxrow = 0, maxcol = 0;
 
-  int[] indices1, indices2;
-
   float[][] elements;
 
   float maxscore;
 
-  private void setRefSeq(SequenceI _refSeq)
-  {
-    refSeq = _refSeq;
-    while (refSeq.getDatasetSequence() != null)
-    {
-      refSeq = refSeq.getDatasetSequence();
-    }
-    length = _refSeq.getEnd() - _refSeq.getStart() + 1;
-  }
 
   @SuppressWarnings("unchecked")
   public PAEContactMatrix(SequenceI _refSeq, Map<String, Object> pae_obj) throws FileFormatException
@@ -105,6 +90,19 @@ public class PAEContactMatrix implements ContactMatrixI
   }
 
   /**
+   * new matrix with specific mapping to a reference sequence
+   * @param newRefSeq
+   * @param newFromMapList
+   * @param elements2
+   */
+  public PAEContactMatrix(SequenceI newRefSeq,
+          MapList newFromMapList, float[][] elements2)
+  {
+    this(newRefSeq,elements2);
+    toSeq = newFromMapList;
+  }
+
+  /**
    * parse a sane JSON representation of the pAE
    * 
    * @param pae_obj
@@ -209,8 +207,22 @@ public class PAEContactMatrix implements ContactMatrixI
   }
 
   @Override
-  public ContactListI getContactList(final int _column)
+  public ContactListI getContactList(final int column)
   {
+    final int _column;
+    if (toSeq != null)
+    {
+      int[] word = toSeq.locateInTo(column, column);
+      if (word == null)
+      {
+        return null;
+      }
+      _column = word[0];
+    }
+    else
+    {
+      _column = column;
+    }
     if (_column < 0 || _column >= elements.length)
     {
       return null;
@@ -231,18 +243,24 @@ public class PAEContactMatrix implements ContactMatrixI
       }
 
       @Override
-      public double getContactAt(int column)
+      public double getContactAt(int mcolumn)
       {
-        if (column < 0 || column >= elements[_column].length)
+        int[] column=(toSeq==null) ? new int[] {mcolumn} : toSeq.locateInTo(mcolumn,mcolumn);
+        if (column==null || column[0] < 0 || column[0] >= elements[_column].length)
         {
           return -1;
         }
-        return elements[_column][column];
+        return elements[_column][column[0]];
       }
     });
   }
 
   @Override
+  protected double getElementAt(int _column, int i)
+  {
+    return elements[_column][i];
+  }
+  @Override
   public float getMin()
   {
     return 0;
@@ -255,18 +273,6 @@ public class PAEContactMatrix implements ContactMatrixI
   }
 
   @Override
-  public boolean hasReferenceSeq()
-  {
-    return (refSeq != null);
-  }
-
-  @Override
-  public SequenceI getReferenceSeq()
-  {
-    return refSeq;
-  }
-
-  @Override
   public String getAnnotDescr()
   {
     return "Predicted Alignment Error"+((refSeq==null) ? "" : (" for " + refSeq.getName()));
@@ -367,10 +373,14 @@ public class PAEContactMatrix implements ContactMatrixI
   @Override
   public BitSet getGroupsFor(int column)
   {
-    for (BitSet gp:groups) {
-      if (gp.get(column))
+    if (groups != null)
+    {
+      for (BitSet gp : groups)
       {
-        return gp;
+        if (gp.get(column))
+        {
+          return gp;
+        }
       }
     }
     return ContactMatrixI.super.getGroupsFor(column);
@@ -443,5 +453,13 @@ public class PAEContactMatrix implements ContactMatrixI
     {
       throw new FileFormatException("No data in PAE matrix read from '"+fileName+"'");
     }
+  }
+
+  @Override
+  protected PAEContactMatrix newMappableContactMatrix(
+          SequenceI newRefSeq, MapList newFromMapList)
+  {
+      return new PAEContactMatrix(newRefSeq, newFromMapList,
+              elements);
   } 
 }
index 6d02630..c56a947 100755 (executable)
@@ -39,6 +39,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureImportSettings;
 import jalview.structure.StructureMapping;
 import jalview.util.Comparison;
+import jalview.ws.datamodel.MappableContactMatrixI;
 
 public class PDBChain
 {
@@ -700,9 +701,9 @@ public class PDBChain
               ana = new AlignmentAnnotation(ana);
               ana.liftOver(dsq, sqmpping);
               dsq.addAlignmentAnnotation(ana);
-              if (cm != null)
+              if (cm != null && cm instanceof MappableContactMatrixI)
               {
-                dsq.addContactListFor(ana, cm);
+                dsq.addContactListFor(ana, ((MappableContactMatrixI) cm).liftOver(dsq,sqmpping));
               }
             }
             else
diff --git a/test/jalview/datamodel/PAEContactMatrixTest.java b/test/jalview/datamodel/PAEContactMatrixTest.java
new file mode 100644 (file)
index 0000000..7426cba
--- /dev/null
@@ -0,0 +1,151 @@
+package jalview.datamodel;
+
+import static org.testng.Assert.*;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import jalview.gui.JvOptionPane;
+import jalview.util.MapList;
+import jalview.ws.datamodel.MappableContactMatrixI;
+import jalview.ws.datamodel.alphafold.PAEContactMatrix;
+
+public class PAEContactMatrixTest
+{
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  static float[][] PAEdata = {
+      {1.0f,2.0f,3.0f,4.0f,5.0f},
+      {2.0f,1.0f,2.0f,3.0f,4.0f},
+      {3.0f,2.0f,1.0f,2.0f,3.0f},
+      {4.0f,3.0f,2.0f,1.0f,2.0f},
+      {5.0f,4.0f,3.0f,2.0f,1.0f}};
+  
+  /**
+   * test associations for a PAE matrix
+   */
+  @Test(groups = { "Functional" })
+  public void testSeqAssociatedPAEMatrix()
+  {
+    Sequence seq = new Sequence("Seq","ASDQE");
+    AlignmentAnnotation aa = seq.addContactList(new PAEContactMatrix(seq, PAEdata));
+    assertNotNull(seq.getContactListFor(aa, 0));
+    assertEquals(seq.getContactListFor(aa, 0).getContactAt(0),1.0);
+    assertNotNull(seq.getContactListFor(aa, 1));
+    assertEquals(seq.getContactListFor(aa, 1).getContactAt(1),1.0);
+    assertNotNull(seq.getContactListFor(aa, 2));
+    assertEquals(seq.getContactListFor(aa, 2).getContactAt(2),1.0);
+    assertNotNull(seq.getContactListFor(aa, 3));
+    assertEquals(seq.getContactListFor(aa, 3).getContactAt(3),1.0);
+    assertNotNull(seq.getContactListFor(aa, 4));
+    assertEquals(seq.getContactListFor(aa, 4).getContactAt(4),1.0);
+    
+    assertNotNull(seq.getContactListFor(aa, seq.getEnd()-1));
+    assertNull(seq.getContactListFor(aa, seq.getEnd()));
+    
+    ContactListI cm = seq.getContactListFor(aa, seq.getStart());
+    assertEquals(cm.getContactAt(seq.getStart()),1d);
+    verifyPAEmatrix(seq, aa, 0,0,4);
+    
+    // Now associated with sequence not starting at 1
+    seq = new Sequence("Seq/5-9","ASDQE");
+    ContactMatrixI paematrix = new PAEContactMatrix(seq, PAEdata);
+    aa = seq.addContactList(paematrix);
+    cm = seq.getContactListFor(aa, 0);
+    assertEquals(cm.getContactAt(0),1d);
+    verifyPAEmatrix(seq, aa, 0, 0, 4);
+    
+    // remap - test the MappableContactMatrix.liftOver method
+    SequenceI newseq = new Sequence("Seq","ASDQEASDQEASDQE");
+    Mapping sqmap = new Mapping(seq, new MapList(new int[] {5,8,10,10},new int[] { 5,9}, 1, 1));
+    assertTrue(paematrix instanceof MappableContactMatrixI);
+    
+    MappableContactMatrixI remapped = ((MappableContactMatrixI)paematrix).liftOver(newseq, sqmap);
+    assertTrue(remapped instanceof PAEContactMatrix);
+    
+    AlignmentAnnotation newaa = newseq.addContactList(remapped);
+    assertNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(1)));
+    assertNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(4)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(5)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(6)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(7)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(8)));
+    // no mapping for position 9
+    assertNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(9)));
+    // last column
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(10)));
+
+    // remap2 - test with original matrix map from 1-5 remapped to 5-9
+    
+    seq = new Sequence("Seq/1-5","ASDQE");
+    paematrix = new PAEContactMatrix(seq, PAEdata);
+    assertTrue(paematrix instanceof MappableContactMatrixI);
+    aa = seq.addContactList(paematrix);
+    
+    newseq = new Sequence("Seq","ASDQEASDQEASDQE");
+    sqmap = new Mapping(seq, new MapList(new int[] {5,9},new int[] { 1,5}, 1, 1));
+    
+    remapped = ((MappableContactMatrixI)paematrix).liftOver(newseq, sqmap);
+    assertTrue(remapped instanceof PAEContactMatrix);
+    
+    
+    newaa = newseq.addContactList(remapped);
+    verify_mapping(newseq, newaa);
+
+    // remap3 - remap2 but mapping sense in liftover is reversed
+    
+    seq = new Sequence("Seq/1-5","ASDQE");
+    paematrix = new PAEContactMatrix(seq, PAEdata);
+    assertTrue(paematrix instanceof MappableContactMatrixI);
+    aa = seq.addContactList(paematrix);
+    
+    newseq = new Sequence("Seq","ASDQEASDQEASDQE");
+    sqmap = new Mapping(newseq, new MapList(new int[] { 1,5},new int[] {5,9},1, 1));
+    
+    remapped = ((MappableContactMatrixI)paematrix).liftOver(newseq, sqmap);
+    assertTrue(remapped instanceof PAEContactMatrix);
+    
+    
+    newaa = newseq.addContactList(remapped);
+    verify_mapping(newseq, newaa);
+    
+  }
+  /**
+   * checks that the PAE matrix is located at positions 1-9 in newseq, and columns are not truncated.
+   * @param newseq
+   * @param newaa
+   */
+  private void verify_mapping(SequenceI newseq, AlignmentAnnotation newaa)
+  {
+  assertNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(1)));
+    assertNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(4)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(5)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(6)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(7)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(8)));
+    assertNotNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(9)));
+    // last column should be null this time
+    assertNull(newseq.getContactListFor(newaa, -1+newseq.findIndex(10)));
+    
+    verifyPAEmatrix(newseq, newaa, 4, 4, 8);
+    
+
+  }
+
+  private void verifyPAEmatrix(SequenceI seq, AlignmentAnnotation aa, int topl, int rowl, int rowr)
+  {
+    for (int f=rowl;f<=rowr;f++) {
+      ContactListI clist = seq.getContactListFor(aa, f);
+      assertNotNull(clist,"No ContactListI for position "+(f));
+      assertEquals(clist.getContactAt(0), (double) f-topl+1,"for column "+f+" relative to "+topl);
+      assertEquals(clist.getContactAt(f-topl),1d, "for column and row "+f+" relative to "+topl);
+    }    
+  }
+
+}