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.Atom;
024import org.biojava.nbio.structure.Calc;
025import org.biojava.nbio.structure.Chain;
026import org.biojava.nbio.structure.cluster.SubunitCluster;
027import org.biojava.nbio.structure.geometry.CalcPoint;
028import org.biojava.nbio.structure.geometry.MomentsOfInertia;
029import org.biojava.nbio.structure.symmetry.utils.SymmetryTools;
030
031import javax.vecmath.Point3d;
032import javax.vecmath.Vector3d;
033
034import java.util.*;
035import java.util.stream.Collectors;
036
037/**
038 * A bean to represent information about the set of {@link org.biojava.nbio.structure.cluster.Subunit}s being
039 * considered for symmetry detection. This class is a helper for the
040 * {@link QuatSymmetryDetector} algorithm, since it calculates and caches the
041 * {@link MomentsOfInertia} and the centroids of each Subunit.
042 *
043 * @author Peter Rose
044 * @author Aleix Lafita
045 *
046 */
047public class QuatSymmetrySubunits {
048
049        private List<Point3d[]> caCoords = new ArrayList<>();
050        private List<Point3d> originalCenters = new ArrayList<>();
051        private List<Point3d> centers = new ArrayList<>();
052        private List<Vector3d> unitVectors = new ArrayList<>();
053
054        private List<Integer> folds = new ArrayList<>();
055        private List<Integer> clusterIds = new ArrayList<>();
056        private List<SubunitCluster> clusters;
057
058        private Point3d centroid;
059        private MomentsOfInertia momentsOfInertia = new MomentsOfInertia();
060
061        /**
062         * Converts the List of {@link SubunitCluster} to a Subunit object.
063         *
064         * @param clusters
065         *            List of SubunitCluster
066         */
067        public QuatSymmetrySubunits(List<SubunitCluster> clusters) {
068
069                this.clusters = clusters;
070
071                // Loop through all subunits in the clusters and fill Lists
072                for (int c = 0; c < clusters.size(); c++) {
073
074                        for (int s = 0; s < clusters.get(c).size(); s++) {
075
076                                clusterIds.add(c);
077                                Atom[] atoms = clusters.get(c).getAlignedAtomsSubunit(s);
078
079                                if( atoms.length == 0) {
080                                        throw new IllegalArgumentException("No aligned atoms in subunit");
081                                }
082
083                                Point3d[] points = Calc.atomsToPoints(atoms);
084
085                                caCoords.add(points);
086                        }
087                }
088
089                // List number of members in each cluster
090                List<Integer> stoichiometries = clusters.stream().map(c -> c.size())
091                                .collect(Collectors.toList());
092                folds = SymmetryTools.getValidFolds(stoichiometries);
093        }
094
095        public List<Point3d[]> getTraces() {
096                return caCoords;
097        }
098
099        public List<Integer> getClusterIds() {
100                return clusterIds;
101        }
102
103        /**
104         * This method is provisional and should only be used for coloring Subunits.
105         * A new coloring schema has to be implemented to allow the coloring of
106         * Subunits, without implying one Subunit = one Chain.
107         *
108         * @return A List of the Chain Ids of each Subunit
109         */
110        public List<String> getChainIds() {
111
112                List<String> chains = new ArrayList<>(getSubunitCount());
113
114                // Loop through all subunits in the clusters and fill Lists
115                for (int c = 0; c < clusters.size(); c++) {
116                        for (int s = 0; s < clusters.get(c).size(); s++)
117                                chains.add(clusters.get(c).getSubunits().get(s).getName());
118                }
119
120                return chains;
121        }
122
123        /**
124         * This method is provisional and should only be used for coloring Subunits.
125         * A new coloring schema has to be implemented to allow the coloring of
126         * Subunits, without implying one Subunit = one Chain.
127         *
128         * @return A List of the Model number of each Subunit
129         */
130        public List<Integer> getModelNumbers() {
131
132                List<Integer> models = new ArrayList<>(getSubunitCount());
133
134                // Loop through all subunits in the clusters and fill Lists
135                for (int c = 0; c < clusters.size(); c++) {
136                        for (int s = 0; s < clusters.get(c).size(); s++) {
137
138                                Atom[] atoms = clusters.get(c).getAlignedAtomsSubunit(s);
139
140                                // TODO guess them chain and model (very ugly)
141                                Chain chain = atoms[0].getGroup().getChain();
142
143                                int model = 0;
144                                for (int m = 0; m < chain.getStructure().nrModels(); m++) {
145                                        if (chain.getStructure().getModel(m).contains(chain)) {
146                                                model = m;
147                                                break;
148                                        }
149                                }
150                                models.add(model);
151                        }
152                }
153                return models;
154        }
155
156        public int getSubunitCount() {
157                run();
158                if (centers == null) {
159                        return 0;
160                }
161                return centers.size();
162        }
163
164        public List<Integer> getFolds() {
165                return folds;
166        }
167
168        public int getCalphaCount() {
169                int count = 0;
170                for (Point3d[] trace : caCoords) {
171                        count += trace.length;
172                }
173                return count;
174        }
175
176        public int getLargestSubunit() {
177                int index = -1;
178                int maxLength = 0;
179                for (int i = 0; i < caCoords.size(); i++) {
180                        int length = caCoords.get(i).length;
181                        if (length > maxLength) {
182                                index = i;
183                        }
184                }
185                return index;
186        }
187
188        public List<Point3d> getCenters() {
189                run();
190                return centers;
191        }
192
193        public List<Vector3d> getUnitVectors() {
194                run();
195                return unitVectors;
196        }
197
198        public List<Point3d> getOriginalCenters() {
199                run();
200                return originalCenters;
201        }
202
203        public Point3d getCentroid() {
204                run();
205                return centroid;
206        }
207
208        public MomentsOfInertia getMomentsOfInertia() {
209                run();
210                return momentsOfInertia;
211        }
212
213        private void run() {
214                if (centers.size() > 0) {
215                        return;
216                }
217                calcOriginalCenters();
218                calcCentroid();
219                calcCenters();
220                calcMomentsOfInertia();
221        }
222
223        private void calcOriginalCenters() {
224                for (Point3d[] trace : caCoords) {
225                        Point3d com = CalcPoint.centroid(trace);
226                        originalCenters.add(com);
227                }
228        }
229
230        private void calcCentroid() {
231                Point3d[] orig = originalCenters.toArray(new Point3d[originalCenters
232                                .size()]);
233                centroid = CalcPoint.centroid(orig);
234        }
235
236        private void calcCenters() {
237                for (Point3d p : originalCenters) {
238                        Point3d c = new Point3d(p);
239                        c.sub(centroid);
240                        centers.add(c);
241                        Vector3d v = new Vector3d(c);
242                        v.normalize();
243                        unitVectors.add(v);
244                }
245        }
246
247        public Point3d getLowerBound() {
248                Point3d lower = new Point3d();
249                for (Point3d p : centers) {
250                        if (p.x < lower.x) {
251                                lower.x = p.x;
252                        }
253                        if (p.y < lower.y) {
254                                lower.y = p.y;
255                        }
256                        if (p.z < lower.z) {
257                                lower.z = p.z;
258                        }
259                }
260                return lower;
261        }
262
263        public Point3d getUpperBound() {
264                Point3d upper = new Point3d();
265                for (Point3d p : centers) {
266                        if (p.x > upper.x) {
267                                upper.x = p.x;
268                        }
269                        if (p.y > upper.y) {
270                                upper.y = p.y;
271                        }
272                        if (p.z > upper.z) {
273                                upper.z = p.z;
274                        }
275                }
276                return upper;
277        }
278
279        private void calcMomentsOfInertia() {
280                for (Point3d[] trace : caCoords) {
281                        for (Point3d p : trace) {
282                                momentsOfInertia.addPoint(p, 1.0f);
283                        }
284                }
285        }
286
287}