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 */
021package org.biojava.nbio.structure.symmetry.core;
022
023import org.biojava.nbio.structure.symmetry.geometry.SuperPosition;
024
025import javax.vecmath.AxisAngle4d;
026import javax.vecmath.Matrix4d;
027import javax.vecmath.Point3d;
028import javax.vecmath.Vector3d;
029
030import java.util.*;
031import java.util.Map.Entry;
032
033public class HelixSolver {
034        private Subunits subunits = null;
035        private int fold = 1;
036        private HelixLayers helixLayers = new HelixLayers();
037        private QuatSymmetryParameters parameters = null;
038        boolean modified = true;
039
040        public HelixSolver(Subunits subunits, int fold, QuatSymmetryParameters parameters) {
041                this.subunits = subunits;
042                this.fold = fold;
043                this.parameters = parameters;
044        }
045
046        public HelixLayers getSymmetryOperations() {
047                if (modified) {
048                        solve();
049                        modified = false;
050                }
051                return helixLayers;
052        }
053
054        private void solve() {
055                if (! preCheck()) {
056                        return;
057                }
058
059                HelicalRepeatUnit unit = new HelicalRepeatUnit(subunits);
060                List<Point3d> repeatUnitCenters = unit.getRepeatUnitCenters();
061                List<Point3d[]> repeatUnits = unit.getRepeatUnits();
062                Set<List<Integer>> permutations = new HashSet<List<Integer>>();
063
064                double minRise = parameters.getMinimumHelixRise() * fold;  // for n-start helix, the rise must be steeper
065                Map<Integer[], Integer> interactionMap = unit.getInteractingRepeatUnits();
066
067                int maxLayerLineLength = 0;
068
069                for (Entry<Integer[], Integer> entry : interactionMap.entrySet()) {
070                        Integer[] pair = entry.getKey();
071                        if (parameters.isVerbose()) {
072                                System.out.println("HelixSolver: pair: " + Arrays.toString(pair));
073                        }
074                        int contacts = entry.getValue();
075                        Point3d[] h1 = null;
076                        Point3d[] h2 = null;
077
078                        // trial superposition of repeat unit pairs to get a seed permutation
079                        h1 = SuperPosition.clonePoint3dArray(repeatUnits.get(pair[0]));
080                        h2 = SuperPosition.clonePoint3dArray(repeatUnits.get(pair[1]));
081                        Matrix4d transformation = SuperPosition.superposeWithTranslation(h1, h2);
082
083                        double rmsd = SuperPosition.rmsd(h1, h2);
084                        double rise = getRise(transformation, repeatUnitCenters.get(pair[0]), repeatUnitCenters.get(pair[1]));
085                        double angle = getAngle(transformation);
086
087                        if (parameters.isVerbose()) {
088                                System.out.println();
089                                System.out.println("Original rmsd: " + rmsd);
090                                System.out.println("Original rise: " + rise);
091                                System.out.println("Original angle: " + Math.toDegrees(angle));
092                        }
093
094                        if (rmsd > parameters.getRmsdThreshold()) {
095                                continue;
096                        }
097
098                        if (Math.abs(rise) < minRise) {
099                                continue;
100                        }
101
102                        // determine which subunits are permuted by the transformation
103                        List<Integer> permutation = getPermutation(transformation);
104
105                        // check permutations for validity
106
107                        // don't save redundant permutations
108                        if (permutations.contains(permutation)) {
109                                continue;
110                        }
111                        permutations.add(permutation);
112                        if (parameters.isVerbose()) {
113                                System.out.println("Permutation: " + permutation);
114                        }
115
116                        // keep track of which subunits are permuted
117                        Set<Integer> permSet = new HashSet<Integer>();
118                        int count = 0;
119                        boolean valid = true;
120                        for (int i = 0; i < permutation.size(); i++) {
121                                if (permutation.get(i) == i) {
122                                        valid = false;
123                                        break;
124                                }
125                                if (permutation.get(i) != -1) {
126                                        permSet.add(permutation.get(i));
127                                        permSet.add(i);
128                                        count++;
129                                }
130
131                        }
132
133                        // a helix a repeat unit cannot map onto itself
134                        if (! valid) {
135                                if (parameters.isVerbose()) {
136                                        System.out.println("Invalid mapping");
137                                }
138                                continue;
139                        }
140
141                        // all subunits must be involved in a permutation
142                        if (permSet.size() != subunits.getSubunitCount()) {
143                                if (parameters.isVerbose()) {
144                                        System.out.println("Not all subunits involved in permutation");
145                                }
146                                continue;
147                        }
148
149                        // if all subunit permutation values are set, then it can't be helical symmetry (must be cyclic symmetry)
150                        if (count == permutation.size()) {
151                                continue;
152                        }
153
154                        // superpose all permuted subunits
155                        List<Point3d> point1 = new ArrayList<Point3d>();
156                        List<Point3d> point2 = new ArrayList<Point3d>();
157                        List<Point3d> centers = subunits.getOriginalCenters();
158                        for (int j = 0; j < permutation.size(); j++) {
159                                if (permutation.get(j) != -1) {
160                                        point1.add(new Point3d(centers.get(j)));
161                                        point2.add(new Point3d(centers.get(permutation.get(j))));
162                                }
163                        }
164
165                        h1 = new Point3d[point1.size()];
166                        h2 = new Point3d[point2.size()];
167                        point1.toArray(h1);
168                        point2.toArray(h2);
169
170                        // calculate subunit rmsd if at least 3 subunits are available
171                        double subunitRmsd = 0;
172                        if (point1.size() > 2) {
173                                transformation = SuperPosition.superposeWithTranslation(h1, h2);
174
175                                subunitRmsd = SuperPosition.rmsd(h1, h2);
176                                rise = getRise(transformation, repeatUnitCenters.get(pair[0]), repeatUnitCenters.get(pair[1]));
177                                angle = getAngle(transformation);
178
179                                if (parameters.isVerbose()) {
180                                        System.out.println("Subunit rmsd: " + subunitRmsd);
181                                        System.out.println("Subunit rise: " + rise);
182                                        System.out.println("Subunit angle: " + Math.toDegrees(angle));
183                                }
184
185                                if (subunitRmsd > parameters.getRmsdThreshold()) {
186                                        continue;
187                                }
188
189                                if (Math.abs(rise) < minRise) {
190                                        continue;
191                                }
192
193                                if (subunitRmsd > parameters.getHelixRmsdToRiseRatio()*Math.abs(rise)) {
194                                        continue;
195                                }
196                        }
197
198                        // superpose all C alpha traces
199                        point1.clear();
200                        point2.clear();
201                        List<Point3d[]> traces = subunits.getTraces();
202                        for (int j = 0; j < permutation.size(); j++) {
203                                if (permutation.get(j) == -1) {
204                                        continue;
205                                }
206                                for (Point3d p: traces.get(j)) {
207                                        point1.add(new Point3d(p));
208                                }
209                                for (Point3d p: traces.get(permutation.get(j))) {
210                                        point2.add(new Point3d(p));
211                                }
212                        }
213
214                        h1 = new Point3d[point1.size()];
215                        h2 = new Point3d[point2.size()];
216                        point1.toArray(h1);
217                        point2.toArray(h2);
218                        Point3d[] h3 = SuperPosition.clonePoint3dArray(h1);
219                        transformation = SuperPosition.superposeWithTranslation(h1, h2);
220
221                        Point3d xtrans = SuperPosition.centroid(h3);
222                        xtrans.negate();
223
224
225                        double traceRmsd = SuperPosition.rmsd(h1, h2);
226
227                        rise = getRise(transformation, repeatUnitCenters.get(pair[0]), repeatUnitCenters.get(pair[1]));
228                        angle = getAngle(transformation);
229
230                        if (parameters.isVerbose()) {
231                                System.out.println("Trace rmsd: " + traceRmsd);
232                                System.out.println("Trace rise: " + rise);
233                                System.out.println("Trace angle: " + Math.toDegrees(angle));
234                                System.out.println("Permutation: " + permutation);
235                        }
236
237                        if (traceRmsd > parameters.getRmsdThreshold()) {
238                                continue;
239                        }
240
241                        if (Math.abs(rise) < minRise) {
242                                continue;
243                        }
244
245                        // This prevents translational repeats to be counted as helices
246                        if (angle < Math.toRadians(parameters.getMinimumHelixAngle())) {
247                                continue;
248                        }
249
250                        if (traceRmsd > parameters.getHelixRmsdToRiseRatio()*Math.abs(rise)) {
251                                continue;
252                        }
253
254                        AxisAngle4d a1 = new AxisAngle4d();
255                        a1.set(transformation);
256
257                        // save this helix rot-translation
258                        Helix helix = new Helix();
259                        helix.setTransformation(transformation);
260                        helix.setPermutation(permutation);
261                        helix.setRise(rise);
262                        // Old version of Vecmath on LINUX doesn't set element m33 to 1. Here we make sure it's 1.
263                        transformation.setElement(3, 3, 1.0);
264                        transformation.invert();
265                        QuatSymmetryScores scores = QuatSuperpositionScorer.calcScores(subunits, transformation, permutation);
266                        scores.setRmsdCenters(subunitRmsd);
267                        helix.setScores(scores);
268                        helix.setFold(fold);
269                        helix.setContacts(contacts);
270                        helix.setRepeatUnits(unit.getRepeatUnitIndices());
271                        if (parameters.isVerbose()) {
272                                System.out.println("Layerlines: " + helix.getLayerLines());
273                        }
274                        for (List<Integer> line: helix.getLayerLines()) {
275                                maxLayerLineLength = Math.max(maxLayerLineLength, line.size());
276                        }
277
278                        // TODO
279        //              checkSelfLimitingHelix(helix);
280
281                        helixLayers.addHelix(helix);
282
283                }
284                if (maxLayerLineLength < 3) {
285//                      System.out.println("maxLayerLineLength: " + maxLayerLineLength);
286                        helixLayers.clear();
287                }
288
289                return;
290        }
291
292        @SuppressWarnings("unused")
293        private void checkSelfLimitingHelix(Helix helix) {
294                HelixExtender he = new HelixExtender(subunits, helix);
295                Point3d[] extendedHelix = he.extendHelix(1);
296
297                int overlap1 = 0;
298                for (Point3d[] trace: subunits.getTraces()) {
299                        for (Point3d pt: trace) {
300                                for (Point3d pe: extendedHelix) {
301                                        if (pt.distance(pe) < 5.0) {
302                                                overlap1++;
303                                        }
304                                }
305                        }
306                }
307
308                extendedHelix = he.extendHelix(-1);
309
310                int overlap2 = 0;
311                for (Point3d[] trace: subunits.getTraces()) {
312                        for (Point3d pt: trace) {
313                                for (Point3d pe: extendedHelix) {
314                                        if (pt.distance(pe) < 3.0) {
315                                                overlap2++;
316                                        }
317                                }
318                        }
319                }
320                System.out.println("SelfLimiting helix: " + overlap1 + ", " + overlap2);
321        }
322
323        private boolean preCheck() {
324                if (subunits.getSubunitCount() < 3) {
325                        return false;
326                }
327                List<Integer> folds = this.subunits.getFolds();
328                int maxFold = folds.get(folds.size()-1);
329                return maxFold > 1;
330        }
331
332        /**
333         * Returns a permutation of subunit indices for the given helix transformation.
334         * An index of -1 is used to indicate subunits that do not superpose onto any other subunit.
335         * @param transformation
336         * @return
337         */
338        private List<Integer> getPermutation(Matrix4d transformation) {
339                double rmsdThresholdSq = Math.pow(this.parameters.getRmsdThreshold(), 2);
340
341                List<Point3d> centers = subunits.getOriginalCenters();
342                List<Integer> seqClusterId = subunits.getSequenceClusterIds();
343
344                List<Integer> permutations = new ArrayList<Integer>(centers.size());
345                double[] dSqs = new double[centers.size()];
346                boolean[] used = new boolean[centers.size()];
347                Arrays.fill(used, false);
348
349                for (int i = 0; i < centers.size(); i++) {
350                        Point3d tCenter = new Point3d(centers.get(i));
351                        transformation.transform(tCenter);
352                        int permutation = -1;
353                        double minDistSq = Double.MAX_VALUE;
354                        for (int j = 0; j < centers.size(); j++) {
355                                if (seqClusterId.get(i) == seqClusterId.get(j)) {
356                                        if (! used[j]) {
357                                                double dSq = tCenter.distanceSquared(centers.get(j));
358                                                if (dSq < minDistSq && dSq <= rmsdThresholdSq) {
359                                                        minDistSq = dSq;
360                                                        permutation = j;
361                                                        dSqs[j] = dSq;
362                                                }
363                                        }
364                                }
365                        }
366                        // can't map to itself
367                        if (permutations.size() == permutation) {
368                                permutation = -1;
369                        }
370
371                        if (permutation != -1) {
372                                used[permutation] = true;
373                        }
374
375
376                        permutations.add(permutation);
377                }
378
379                return permutations;
380        }
381
382
383
384        /**
385         * Returns the rise of a helix given the subunit centers of two adjacent
386         * subunits and the helix transformation
387         * @param transformation helix transformation
388         * @param p1 center of one subunit
389         * @param p2 center of an adjacent subunit
390         * @return
391         */
392        private static double getRise(Matrix4d transformation, Point3d p1, Point3d p2) {
393                AxisAngle4d axis = getAxisAngle(transformation);
394                Vector3d h = new Vector3d(axis.x, axis.y, axis.z);
395                Vector3d p = new Vector3d();
396                p.sub(p1, p2);
397                return p.dot(h);
398        }
399
400        /**
401         * Returns the pitch angle of the helix
402         * @param transformation helix transformation
403         * @return
404         */
405        private static double getAngle(Matrix4d transformation) {
406                return getAxisAngle(transformation).angle;
407        }
408
409        /**
410         * Returns the AxisAngle of the helix transformation
411         * @param transformation helix transformation
412         * @return
413         */
414        private static AxisAngle4d getAxisAngle(Matrix4d transformation) {
415                AxisAngle4d axis = new AxisAngle4d();
416                axis.set(transformation);
417                return axis;
418        }
419}