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.internal;
022
023import static java.lang.Math.*;
024
025import org.biojava.nbio.structure.Atom;
026import org.biojava.nbio.structure.align.model.AFPChain;
027import org.biojava.nbio.structure.align.util.RotationAxis;
028import org.biojava.nbio.structure.symmetry.internal.OrderDetector;
029import org.biojava.nbio.structure.symmetry.internal.RefinerFailedException;
030
031/**
032 * Guesses an order of rotational symmetry from the angle.
033 * <p>
034 * Improves upon the AngleOrderDetector used in the paper by checking all
035 * rotations, not just the base one.
036 *
037 * @author Spencer Bliven
038 * @since 4.2.0
039 *
040 */
041public class AngleOrderDetectorPlus implements OrderDetector {
042
043        private int maxOrder;
044        private final double error;
045        private boolean normalizeError;
046
047        /**
048         * @param error
049         *            maximum angular error, in radians
050         */
051        public AngleOrderDetectorPlus(double angleError) {
052                this(8, angleError, false);
053        }
054
055        public AngleOrderDetectorPlus(int maxOrder) {
056                // PI is the max error for C1
057                this(maxOrder, PI, false);
058        }
059
060        /**
061         *
062         * @param maxOrder
063         *            maximum order to consider
064         * @param error
065         *            maximum angular error, in radians
066         */
067        public AngleOrderDetectorPlus(int maxOrder, double angleError) {
068                this(maxOrder, angleError, false);
069        }
070
071        /**
072         * Determine order by finding the order (up to the maxOrder) which has the
073         * closest rotation angle to the observed rotation.
074         *
075         * If normalized is false, then the error is taken to be the absolute error
076         * from the closest ideal angle (in radians). If normalized is true, error
077         * is taken to be relative to the fundamental rotation for a given order.
078         * For instance, for an error of .25, C2 order would be accepted for angles
079         * within .25*pi radians of 0 or pi, while C3 order would be acceptable
080         * within .25*2pi/3 radians of 0, 2pi/3, or 4pi/3. In the normalized case,
081         * numbers between 0 and .5 are sensible for error.
082         *
083         * @param maxOrder
084         *            maximum order to consider
085         * @param error
086         *            maximum angular error
087         * @param normalize
088         *            indicates whether error should be normalized by the order
089         */
090        public AngleOrderDetectorPlus(int maxOrder, double angleError,
091                        boolean normalize) {
092                super();
093                this.maxOrder = maxOrder;
094                this.error = angleError;
095                this.normalizeError = normalize;
096        }
097
098        @Override
099        public int calculateOrder(AFPChain afpChain, Atom[] ca)
100                        throws RefinerFailedException {
101                final double tol = 1e-6; // tolerance to floating point errors
102                try {
103                        RotationAxis axis = new RotationAxis(afpChain);
104                        double theta = axis.getAngle();
105
106                        double bestDelta = error;
107                        int bestOrder = 1;
108                        for (int order = 1; order <= maxOrder; order++) {
109                                // Triangle wave starting at 0 with period 2pi/order
110                                double delta = abs(abs(theta * order / (2 * PI) - .5) % 1.0 - .5);
111                                // Triangle waves have amplitude 1, so need to un-normalize
112                                if (!normalizeError)
113                                        delta *= 2 * PI / order;
114
115                                if (delta < bestDelta - tol) {
116                                        bestOrder = order;
117                                        bestDelta = delta;
118                                }
119                        }
120                        return bestOrder;
121                } catch (Exception e) {
122                        throw new RefinerFailedException(e);
123                }
124        }
125
126        @Override
127        public String toString() {
128                return getClass().getSimpleName() + "[maxOrder=" + maxOrder
129                                + ", error=" + error + ", normalizeError=" + normalizeError
130                                + "]";
131        }
132
133}