
org.biojava.nbio.structure.symmetry.internal.SymmetryAxes Maven / Gradle / Ivy
Show all versions of biojava-structure Show documentation
/*
* BioJava development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public Licence. This should
* be distributed with the code. If you do not have a copy,
* see:
*
* http://www.gnu.org/copyleft/lesser.html
*
* Copyright for this code is held jointly by the individual
* authors. These should be listed in @author doc comments.
*
* For more information on the BioJava project and its aims,
* or to join the biojava-l mailing list, visit the home page
* at:
*
* http://www.biojava.org/
*
*/
package org.biojava.nbio.structure.symmetry.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.vecmath.Matrix4d;
import org.biojava.nbio.structure.align.multiple.MultipleAlignment;
import org.biojava.nbio.structure.align.util.RotationAxis;
import org.biojava.nbio.structure.symmetry.internal.CESymmParameters.SymmetryType;
/**
* Data Structure that stores all the symmetry axis that describe
* the symmetry of a structure. Generalizes to all types of symmetry,
* the classic ones (Cn, Dn) and any hierarchical or local symmetries.
*
* Hierarchical symmetry can be visualized as a tree, where each level
* has a fixed branching factor. Each level of the tree is associated
* with a transformation operator, whose order determines the degree of
* nodes at that level of the tree. Leaves of the tree implicitly
* represent aligned repeats (indexed 0 to n-1), so care must be taken to
* keep external references to the repeats (e.g. rows of a
* {@link MultipleAlignment} in the same order implied by the tree.
*
* Each node of the tree specifies an alignment between those repeats
* below each of its children. It is also associated with a symmetry axis,
* which is calculated based on the associated operator as well as any parent
* operators.
* It also stores the parts of the structure (symmetric units) involved
* in each axis, in addition to the way to calculate them.
*
* This is intended to provide a general axis support for the multiple
* repeat alignment optimization and the axis display in Jmol. This
* object is related to a MultipleAlignment object that defines the
* symmetric units.
*
* @author Aleix Lafita
* @since 4.2.0
*
*/
public class SymmetryAxes {
/*
* Implementation note: The tree is a nice explanation and a good image
* for developing algorithms, but it is not constructed explicitly.
* Instead, we just store one elementary axis for each level and reconstruct
* which operators apply to a particular leaf based on that leaf's index.
*/
/**
* Represents an axis of symmetry
* @author Spencer Bliven
*
*/
public static class Axis {
private Matrix4d operator;
private int order;
private SymmetryType symmType;
private int level;
//private int indexInLevel;
private int firstRepeat;
private RotationAxis rotAxis;
public Axis(Matrix4d operator, int order, SymmetryType type, int level, int firstRepeat) {
if (order < 2) {
throw new IllegalArgumentException("A symmetry axis should divide a structure in > 2 parts");
}
if(type != SymmetryType.OPEN && type != SymmetryType.CLOSED) {
throw new IllegalArgumentException("Invalid symmetry type. Only OPEN and CLOSED are allowed");
}
this.operator = operator;
this.order = order;
this.symmType = type;
setLevel(level);
setFirstRepeat(firstRepeat);
rotAxis = null;
}
/**
* Get the transformation operator for this axis as an homogeneous matrix
* @return the transformation operator
*/
public Matrix4d getOperator() {
return operator;
}
public void setOperator(Matrix4d op) {
this.operator = op;
}
/**
* Get the order of this axis (closed symm) or the number of repeats
* (open symm)
* @return the order
*/
public int getOrder() {
return order;
}
/**
* @return the symmType (OPEN or CLOSED only)
*/
public SymmetryType getSymmType() {
return symmType;
}
/**
* Get the transformation operator as a rotation axis. For open
* symmetry this will have a non-zero screw component.
* @return a RotationAxis for this Axis
*/
public RotationAxis getRotationAxis() {
if( rotAxis == null) {
rotAxis = new RotationAxis(operator);
}
return rotAxis;
}
/**
* @return The level of this axis within it's parent hierarchy, or -1 if unset
*/
public int getLevel() {
return level;
}
/**
*
* @param level The level of this axis within it's parent hierarchy. Must be positive
*/
public void setLevel(int level) {
if(level < 0) throw new IndexOutOfBoundsException("Level must be positive");
this.level = level;
}
// /**
// * Each level can contain multiple equivalent axes. This index is
// * used to distinguish them.
// * @return the index of this axis relative to others at the same level
// */
// public int getIndexInLevel() {
// return indexInLevel;
// }
// /**
// *
// * @param indexInLevel the index of this axis relative to others at the same level
// */
// public void setIndexInLevel(int indexInLevel) {
// if( indexInLevel < 0 || getOrder() <= indexInLevel )
// throw new IndexOutOfBoundsException("Invalid index for order "+getOrder());
// this.indexInLevel = indexInLevel;
// }
/**
* Get the index of the first repeat used by this axis
* @return the firstRepeat
*/
public int getFirstRepeat() {
return firstRepeat;
}
/**
* @param firstRepeat the index of the first repeat used by this axis
*/
public void setFirstRepeat(int firstRepeat) {
this.firstRepeat = firstRepeat;
}
}
/**
* List of all symmetry axis. They are sorted from higher to lower
* in the symmetry hierarchy, where higher means that they apply
* more globally and lower means that they apply to a local region
* of the higher axis division.
*/
private final List axes;
/**
* Constructor.
* Initializes variables only.
*/
public SymmetryAxes(){
axes = new ArrayList<>();
}
/**
* Adds a new axis of symmetry to the bottom level of the tree
*
* @param axis the new axis of symmetry found
* @param order number of parts that this axis divides the structure in
* @param type indicates whether the axis has OPEN or CLOSED symmetry
*/
public void addAxis(Matrix4d axis, int order, SymmetryType type) {
axes.add(new Axis(axis,order,type,axes.size(),0));
}
/**
* Return a list giving the number of times each axis must be applied
* to generate the given repeat.
*
* For instance, for a D3 case getAxisCounts(4) would return [2,0],
* indicating that repeat 4 is generated by two applications of the 3-fold
* axis followed by 0 applications of the two-fold axis.
*
* @param repeat Index of the desired repeat
* @return array of the same length as axes giving the number of times
* to apply each axis.
*/
private int[] getAxisCounts(int repeat) {
int[] counts = new int[getNumLevels()];
for(int i = counts.length-1; i >= 0; i--) {
int d = axes.get(i).getOrder();
counts[i] = repeat % d;
repeat /= d;
}
assert repeat == 0 : "Invalid repeat index";
return counts;
}
// /**
// * Inverse of {@link #getAxisCounts(int)}; Calculates the repeat for a
// * particular number of applications of each axis
// * @param counts Number of times to apply each axis
// * @return Repeat index
// */
// private int getRepeatIndex(int[] counts) {
// int repeat = 0;
// for(int i = 0; i< counts.length; i++) {
// repeat += counts[i]*axes.get(i).getOrder();
// }
// return repeat;
// }
/**
* Updates an axis of symmetry, after the superposition changed.
*
* @param index old axis index
* @param newAxis
*/
public void updateAxis(Integer index, Matrix4d newAxis){
axes.get(index).setOperator(newAxis);
}
/**
* Return the operator for all elementary axes of symmetry of the structure, that is,
* the axes stored in the List as unique and from which all the symmetry
* axes are constructed.
*
* @return axes elementary axes of symmetry.
*/
public List getElementaryAxes(){
List ops = new ArrayList(getNumLevels());
for(Axis axis : axes) {
ops.add(axis.getOperator());
}
return ops;
}
/**
* Return all elementary axes of symmetry of the structure, that is,
* the axes stored in the List as unique and from which all the symmetry
* axes are constructed.
*
* @return axes elementary axes of symmetry.
*/
public List getElementaryAxesObjects() {
return axes;
}
/**
* Get the indices of participating repeats in Cauchy two-line form.
*
* Returns two lists of the same length.
* The first gives a list of all repeat indices which are aligned
* at the specified level of symmetry (e.g. 0 through the degree of this level).
* The second list gives the corresponding repeats after applying the
* operator once.
*
* @param level the axis index
* @return the double List of repeat relations, or null if the
* level is invalid
* @see #getRepeatsCyclicForm(int, int) for an equivalent specification with half the memory
*/
public List> getRepeatRelation(int level){
return getRepeatRelation(level,0);
}
public List> getRepeatRelation(Axis axis){
return getRepeatRelation(axis.getLevel(),axis.getFirstRepeat());
}
public List> getRepeatRelation(int level, int firstRepeat) {
Axis axis = axes.get(level);
int m = getNumRepeats(level+1);//size of the children
int d = axis.getOrder(); // degree of this node
int n = m*d; // number of repeats included
if(firstRepeat % n != 0)
throw new IllegalArgumentException(String.format("Repeat %d cannot start a block at level %s of this tree",firstRepeat,level));
if(axis.getSymmType() == SymmetryType.OPEN) {
n -= m; // leave off last child for open symm
}
List repeats = new ArrayList<>(n);
List equiv = new ArrayList<>(n);
for(int i=0;i
* Each inner list gives a set of equivalent repeats and should have length
* equal to the order of the axis' operator.
* @param level
* @param firstRepeat
* @return
*/
public List> getRepeatsCyclicForm(int level, int firstRepeat) {
Axis axis = axes.get(level);
int m = getNumRepeats(level+1);//size of the children
int d = axis.getOrder(); // degree of this node
int n = m*d; // number of repeats included
if(firstRepeat % n != 0) {
throw new IllegalArgumentException(String.format("Repeat %d cannot start a block at level %s of this tree",firstRepeat,level));
}
if(axis.getSymmType() == SymmetryType.OPEN) {
n -= m; // leave off last child for open symm
}
List> repeats = new ArrayList<>(m);
for(int i=0;i cycle = new ArrayList<>(d);
for(int j=0;j> getRepeatsCyclicForm(Axis axis) {
return getRepeatsCyclicForm(axis.getLevel(),axis.getFirstRepeat());
}
public List> getRepeatsCyclicForm(int level) {
return getRepeatsCyclicForm(level,0);
}
public String getRepeatsCyclicForm(Axis axis, List> repeats) {
if(repeats.size() != getNumRepeats()) {
throw new IllegalArgumentException("Mismatch in the number of repeats");
}
return getRepeatsCyclicForm(getRepeatsCyclicForm(axis), repeats);
}
public static String getRepeatsCyclicForm(List> cycleForm, List> repeats) {
StringBuilder str = new StringBuilder();
for(List cycle : cycleForm) {
str.append("(");
Iterator cycleIt = cycle.iterator();
str.append(repeats.get(cycleIt.next())); //should be at least one
while(cycleIt.hasNext()) {
str.append(";")
.append(repeats.get( cycleIt.next() ));
}
str.append(")");
}
return str.toString();
}
/**
* Return the transformation that needs to be applied to a
* repeat in order to superimpose onto repeat 0.
*
* @param repeat the repeat index
* @return transformation matrix for the repeat
*/
public Matrix4d getRepeatTransform(int repeat){
Matrix4d transform = new Matrix4d();
transform.setIdentity();
int[] counts = getAxisCounts(repeat);
for(int t = counts.length-1; t>=0; t--) {
if( counts[t] == 0 )
continue;
Matrix4d axis = new Matrix4d(axes.get(t).getOperator());
for(int i=0;i=0; t--) {
if(counts[t] == 0)
continue;
if (counts[t] > 0) {
Matrix4d axis = new Matrix4d(axes.get(t).getOperator());
for(int i=0;i getSymmetryAxes(){
List symmAxes = new ArrayList<>();
Matrix4d prior = new Matrix4d();
prior.setIdentity();
getSymmetryAxes(symmAxes,prior,0,0);
return symmAxes;
}
/**
* Recursive helper
* @param symmAxes output list
* @param prior transformation aligning the first repeat of this axis with the first overall
* @param level current level
*/
private void getSymmetryAxes(List symmAxes, Matrix4d prior, int level, int firstRepeat) {
if(level >= getNumLevels() ) {
return;
}
Axis elem = axes.get(level);
Matrix4d elemOp = elem.getOperator();
// Current axis:
// elementary maps B -> A
// prior maps I -> A and J -> B
// want J -> I = J -> B -> A <- I= inv(prior) * elementary * prior
Matrix4d currAxisOp = new Matrix4d(prior);
currAxisOp.invert();
currAxisOp.mul(elemOp);
currAxisOp.mul(prior);
Axis currAxis = new Axis(currAxisOp,elem.getOrder(),elem.getSymmType(),level,firstRepeat);
symmAxes.add(currAxis);
//Remember that all degrees are at least 2
getSymmetryAxes(symmAxes,prior,level+1,firstRepeat);
//New prior is elementary^d*prior
Matrix4d newPrior = new Matrix4d(elemOp);
newPrior.mul(prior);
int childSize = getNumRepeats(level+1);
getSymmetryAxes(symmAxes,newPrior,level+1,firstRepeat+childSize);
for(int d=2;d= degrees.get(level-1) )
// throw new IndexOutOfBoundsException("Axis number out of bounds");
// // Convert axisNum into a count of
//
// }
/**
* Get the number of repeats. This is equal to the product of all degrees.
* @return Number of repeats (leaves of the tree).
*/
public int getNumRepeats() {
return getNumRepeats(0);
}
/**
* Get the number of leaves from a node at the specified level. This is
* equal to the product of all degrees at or below the level.
* @param level level of the tree to cut at
* @return Number of repeats (leaves of the tree).
*/
private int getNumRepeats(int level) {
int size = 1;
// Return 1 for illegally high level
if(level < getNumLevels()) {
for(Axis axis : axes.subList(level, getNumLevels())) {
size *= axis.getOrder();
}
}
return size;
}
/**
* Get the first repeat index of each axis of a specified level.
* @param level level of the tree to cut at
* @return List of first Repeats of each index, sorted in ascending order
*/
public List getFirstRepeats(int level) {
List firstRepeats = new ArrayList<>();
int m = getNumRepeats(level+1); //size of the level
int d = axes.get(level).getOrder(); //degree of this level
int n = m*d; // number of repeats included in each axis
for (int firstRepeat = 0; firstRepeat < getNumRepeats(); firstRepeat+=n)
firstRepeats.add(firstRepeat);
return firstRepeats;
}
public Axis getElementaryAxis(int level) {
return axes.get(level);
}
public int getNumLevels() {
return axes.size();
}
}