001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.nbio.structure.symmetry.core;
023
024import org.biojava.nbio.structure.geometry.CalcPoint;
025import org.biojava.nbio.structure.geometry.UnitQuaternions;
026import org.biojava.nbio.structure.symmetry.utils.PermutationGenerator;
027
028import javax.vecmath.AxisAngle4d;
029import javax.vecmath.Matrix4d;
030import javax.vecmath.Point3d;
031import javax.vecmath.Quat4d;
032import javax.vecmath.Vector3d;
033
034import java.util.ArrayList;
035import java.util.HashSet;
036import java.util.List;
037import java.util.Set;
038
039
040/**
041 *
042 * @author Peter
043 */
044public class SystematicSolver implements QuatSymmetrySolver {
045        private QuatSymmetrySubunits subunits = null;
046        private QuatSymmetryParameters parameters = null;
047
048        private Point3d[] originalCoords = null;
049        private Point3d[] transformedCoords = null;
050        private RotationGroup rotations = new RotationGroup();
051        private Vector3d centroid = new Vector3d();
052        private Matrix4d centroidInverse = new Matrix4d();
053        private Set<List<Integer>> hashCodes = new HashSet<List<Integer>>();
054
055        public SystematicSolver(QuatSymmetrySubunits subunits, QuatSymmetryParameters parameters) {
056                if (subunits.getSubunitCount()== 2) {
057                        throw new IllegalArgumentException("SystematicSolver cannot be applied to subunits with 2 centers");
058                }
059                this.subunits = subunits;
060                this.parameters = parameters;
061        }
062
063        @Override
064        public RotationGroup getSymmetryOperations() {
065                if (rotations.getOrder() == 0) {
066                        solve();
067                        rotations.complete();
068                }
069                return rotations;
070        }
071
072        private void solve() {
073                initialize();
074                int n = subunits.getSubunitCount();
075                PermutationGenerator g = new PermutationGenerator(n);
076
077                // loop over all permutations
078                while (g.hasMore()) {
079                        int[] perm = g.getNext();
080                        List<Integer> permutation = new ArrayList<Integer>(perm.length);
081                        for (int j = 0; j < n; j++) {
082                                permutation.add(perm[j]);
083                        }
084
085                        if (! isValidPermutation(permutation)) {
086                                continue;
087                        }
088
089                        boolean newPermutation = evaluatePermutation(permutation);
090                        if (newPermutation) {
091                                completeRotationGroup();
092                        }
093
094                        if (rotations.getOrder() >= subunits.getSubunitCount()) {
095                                return;
096                        }
097                }
098        }
099
100        /**
101         * Adds translational component to rotation matrix
102         * @param rotTrans
103         * @param rotation
104         * @return
105         */
106        private void combineWithTranslation(Matrix4d rotation) {
107                rotation.setTranslation(centroid);
108                rotation.mul(rotation, centroidInverse);
109        }
110
111        private Rotation createSymmetryOperation(List<Integer> permutation, Matrix4d transformation, AxisAngle4d axisAngle, int fold, QuatSymmetryScores scores) {
112                Rotation s = new Rotation();
113                s.setPermutation(new ArrayList<Integer>(permutation));
114                s.setTransformation(new Matrix4d(transformation));
115                s.setAxisAngle(new AxisAngle4d(axisAngle));
116                s.setFold(fold);
117                s.setScores(scores);
118                return s;
119        }
120
121        private void completeRotationGroup() {
122                PermutationGroup g = new PermutationGroup();
123                for (int i = 0; i < rotations.getOrder(); i++) {
124                        Rotation s = rotations.getRotation(i);
125                        g.addPermutation(s.getPermutation());
126                }
127                g.completeGroup();
128
129//      System.out.println("Completing rotation group from: " +symmetryOperations.getSymmetryOperationCount() + " to " + g.getPermutationCount());
130
131                // the group is complete, nothing to do
132                if (g.getOrder() == rotations.getOrder()) {
133                        return;
134                }
135
136//      System.out.println("complete group: " +  rotations.getOrder() +"/" + g.getOrder());
137                // try to complete the group
138                for (int i = 0; i < g.getOrder(); i++) {
139                        List<Integer> permutation = g.getPermutation(i);
140                        if (isValidPermutation(permutation)) {
141                                  // perform permutation of subunits
142                                evaluatePermutation(permutation);
143                        }
144                }
145        }
146
147        private boolean isValidPermutation(List<Integer> permutation) {
148                if (permutation.size() == 0) {
149                        return false;
150                }
151
152                // if this permutation is a duplicate, return false
153                if (hashCodes.contains(permutation)) {
154                        return false;
155                }
156
157                // check if permutation is pseudosymmetric
158                if (! isAllowedPermuation(permutation)) {
159                        return false;
160                }
161
162                // get fold and make sure there is only one E (fold=1) permutation
163                int fold = PermutationGroup.getOrder(permutation);
164                if (rotations.getOrder() > 1 && fold == 1) {
165                        return false;
166                }
167                if (fold == 0 || subunits.getSubunitCount() % fold != 0) {
168                        return false;
169                }
170
171                // if this permutation is a duplicate, returns false
172                return hashCodes.add(permutation);
173        }
174
175        private boolean isAllowedPermuation(List<Integer> permutation) {
176                List<Integer> seqClusterId = subunits.getClusterIds();
177                for (int i = 0; i < permutation.size(); i++) {
178                        int j = permutation.get(i);
179                        if (seqClusterId.get(i) != seqClusterId.get(j)) {
180                                return false;
181                        }
182                }
183                return true;
184        }
185
186        private boolean evaluatePermutation(List<Integer> permutation) {
187                // permutate subunits
188                for (int j = 0, n = subunits.getSubunitCount(); j < n; j++) {
189                        transformedCoords[j].set(originalCoords[permutation.get(j)]);
190                }
191
192                int fold = PermutationGroup.getOrder(permutation);
193                
194                // TODO implement this piece of code using at origin superposition
195                Quat4d quat = UnitQuaternions.relativeOrientation(
196                                originalCoords, transformedCoords);
197                AxisAngle4d axisAngle = new AxisAngle4d();
198                Matrix4d transformation = new Matrix4d();
199                
200                transformation.set(quat);
201                axisAngle.set(quat);
202                
203                Vector3d axis = new Vector3d(axisAngle.x, axisAngle.y, axisAngle.z);
204                if (axis.lengthSquared() < 1.0E-6) {
205                        axisAngle.x = 0;
206                        axisAngle.y = 0;
207                        axisAngle.z = 1;
208                        axisAngle.angle = 0;
209                } else {
210                        axis.normalize();
211                        axisAngle.x = axis.x;
212                        axisAngle.y = axis.y;
213                        axisAngle.z = axis.z;
214                }
215                
216                CalcPoint.transform(transformation, transformedCoords);
217                
218                double subunitRmsd = CalcPoint.rmsd(transformedCoords, originalCoords);
219
220                if (subunitRmsd <parameters.getRmsdThreshold()) {
221                        // transform to original coordinate system
222                        combineWithTranslation(transformation);
223                        QuatSymmetryScores scores = QuatSuperpositionScorer.calcScores(subunits, transformation, permutation);
224                        if (scores.getRmsd() < 0.0 || scores.getRmsd() > parameters.getRmsdThreshold()) {
225                                return false;
226                        }
227
228                        scores.setRmsdCenters(subunitRmsd);
229                        Rotation symmetryOperation = createSymmetryOperation(permutation, transformation, axisAngle, fold, scores);
230                        rotations.addRotation(symmetryOperation);
231                        return true;
232                }
233                return false;
234        }
235
236        private void initialize() {
237                // translation to centered coordinate system
238                centroid = new Vector3d(subunits.getCentroid());
239
240                // translation back to original coordinate system
241                Vector3d reverse = new Vector3d(centroid);
242                reverse.negate();
243                centroidInverse.set(reverse);
244                // Make sure matrix element m33 is 1.0. An old version vecmath did not set this element.
245                centroidInverse.setElement(3, 3, 1.0);
246
247                List<Point3d> centers = subunits.getCenters();
248                int n = subunits.getSubunitCount();
249
250                originalCoords = new Point3d[n];
251                transformedCoords = new Point3d[n];
252
253                for (int i = 0; i < n; i++) {
254                        originalCoords[i] = centers.get(i);
255                        transformedCoords[i] = new Point3d();
256                }
257        }
258}