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.symmetry.geometry.MomentsOfInertia;
025import org.biojava.nbio.structure.symmetry.geometry.SuperPosition;
026
027import javax.vecmath.Point3d;
028import javax.vecmath.Vector3d;
029import java.util.*;
030import java.util.Map.Entry;
031
032
033/**
034 * A bean to represent info about the set of subunits being considered for a
035 * QuatSymmetryDetector alignment.
036 * @author Peter Rose
037 */
038public class Subunits {
039        private List<Point3d[]> caCoords = Collections.emptyList();
040        private List<Integer> sequenceClusterIds = Collections.emptyList();
041        private List<Boolean> pseudoStoichiometry = Collections.emptyList();
042        private List<Double> minSequenceIdentity = Collections.emptyList();
043        private List<Double> maxSequenceIdentity = Collections.emptyList();
044        private List<String> chainIds = Collections.emptyList();
045        private List<Integer> modelNumbers =  Collections.emptyList();
046        private List<Integer> folds = Collections.emptyList();
047        private List<Point3d> originalCenters = new ArrayList<Point3d>();
048        private List<Point3d> centers = new ArrayList<Point3d>();
049        private List<Vector3d> unitVectors = new ArrayList<Vector3d>();
050        private int nucleicAcidChainCount = 0;
051        private boolean pseudoSymmetric = false;
052
053        private Point3d centroid;
054        private MomentsOfInertia momentsOfInertia = new MomentsOfInertia();
055
056        /**
057         * All inputs should contain one element per subunit.
058         * @param caCoords CA coordinates of all subunits
059         * @param sequenceClusterIds ID of the cluster that each subunit belongs to
060         * @param pseudoStoichiometry Whether pseudosymmetry was used when clustering the subunit
061         * @param minSequenceIdentity Minimum sequence identity to other cluster members
062         * @param maxSequenceIdentity Maximum sequence identity to other cluster members
063         * @param folds Valid symmetry orders for this stoichiometry
064         * @param chainIds Chain ID for the subunit
065         * @param modelNumbers Model number for the subunit
066         */
067        public Subunits(List<Point3d[]> caCoords, List<Integer> sequenceClusterIds, List<Boolean> pseudoStoichiometry, List<Double> minSequenceIdentity, List<Double> maxSequenceIdentity, List<Integer> folds, List<String> chainIds, List<Integer> modelNumbers) {
068                
069                for (int i=0; i<caCoords.size(); i++) {
070                        if (caCoords.get(i).length==0) 
071                                throw new IllegalArgumentException("0-length coordinate array in subunit coordinates with index " +i+". Can't calculate quaternary symmetry for empty coordinates.");
072                }
073                
074                this.caCoords = caCoords;
075                this.sequenceClusterIds = sequenceClusterIds;
076                this.pseudoStoichiometry = pseudoStoichiometry;
077                this.minSequenceIdentity = minSequenceIdentity;
078                this.maxSequenceIdentity = maxSequenceIdentity;
079                this.folds = folds;
080                this.chainIds = chainIds;
081                this.modelNumbers = modelNumbers;
082        }
083
084        public List<Point3d[]> getTraces() {
085                return caCoords;
086        }
087
088        public int getSubunitCount() {
089                run();
090                if (centers == null) {
091                        return 0;
092                }
093                return centers.size();
094        }
095
096        public List<Integer> getSequenceClusterIds() {
097                return sequenceClusterIds;
098        }
099
100        public boolean isPseudoStoichiometric() {
101                for (Boolean b: pseudoStoichiometry) {
102                        if (b) {
103                                return true;
104                        }
105                }
106                return false;
107        }
108
109        public boolean isPseudoSymmetric() {
110                return pseudoSymmetric;
111        }
112
113        public void setPseudoSymmetric(boolean pseudoSymmetric) {
114                this.pseudoSymmetric = pseudoSymmetric;
115        }
116
117        public double getMinSequenceIdentity() {
118                double minId = 1.0;
119                for (double seqId: minSequenceIdentity) {
120                        minId = Math.min(seqId,  minId);
121                }
122                return minId;
123        }
124
125        public double getMaxSequenceIdentity() {
126                double maxId = 1.0;
127                for (double seqId: maxSequenceIdentity) {
128                        maxId = Math.min(seqId,  maxId);
129                }
130                return maxId;
131        }
132
133        public List<String> getChainIds() {
134                return chainIds;
135        }
136
137        public List<Integer> getModelNumbers() {
138                return modelNumbers;
139        }
140
141        public List<Integer>getFolds() {
142                return folds;
143        }
144
145        public String getStoichiometry() {
146                // count number of members in each cluster
147                Map<Integer, Integer> map = new TreeMap<Integer, Integer>();
148                for (Integer id: sequenceClusterIds) {
149                        Integer value = map.get(id);
150                        if (value == null) {
151                                value = new Integer(1);
152                        } else {
153                                value++;
154                        }
155                        map.put(id, value);
156                }
157
158                // build formula string
159                String alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
160                StringBuilder formula = new StringBuilder();
161                for (Entry<Integer, Integer> entry: map.entrySet()) {
162                        String key = "?";
163                        int id = entry.getKey();
164                        if (id < alpha.length()) {
165                                key = alpha.substring(id, id+1);
166                        }
167                        formula.append(key);
168                        if (entry.getValue() > 1) {
169                                formula.append(entry.getValue());
170                        }
171                }
172
173                return formula.toString();
174        }
175
176        public int getCalphaCount() {
177                int count = 0;
178                for (Point3d[] trace: caCoords) {
179                        count += trace.length;
180                }
181                return count;
182        }
183
184        public int getLargestSubunit() {
185                int index = -1;
186                int maxLength = 0;
187                for (int i = 0; i < caCoords.size(); i++) {
188                        int length = caCoords.get(i).length;
189                        if (length > maxLength) {
190                                index = i;
191                        }
192                }
193                return index;
194        }
195
196        public List<Point3d> getCenters() {
197                run();
198                return centers;
199        }
200
201        public List<Vector3d> getUnitVectors() {
202                run();
203                return unitVectors;
204        }
205
206        public List<Point3d> getOriginalCenters() {
207                run();
208                return originalCenters;
209        }
210
211        public Point3d getCentroid() {
212                run();
213                return centroid;
214        }
215
216        public MomentsOfInertia getMomentsOfInertia() {
217                run();
218                return momentsOfInertia;
219        }
220
221        /**
222         * @return the nucleicAcidChainCount
223         */
224        public int getNucleicAcidChainCount() {
225                run();
226                return nucleicAcidChainCount;
227        }
228
229        /**
230         * @param nucleicAcidChainCount the nucleicAcidChainCount to set
231         */
232        public void setNucleicAcidChainCount(int nucleicAcidChainCount) {
233                this.nucleicAcidChainCount = nucleicAcidChainCount;
234        }
235
236        public boolean overlaps(Subunits subunits) {
237                Set<String> set1 = getSignatures(this);
238                Set<String> set2 = getSignatures(subunits);
239                set1.retainAll(set2);
240                return set1.size() > 0;
241        }
242
243        public boolean contains(Subunits subunits) {
244                Set<String> set1 = getSignatures(this);
245                Set<String> set2 = getSignatures(subunits);
246                return set1.containsAll(set2);
247        }
248
249        private static Set<String> getSignatures(Subunits subunits) {
250                Set<String> set = new HashSet<String>(subunits.getSubunitCount());
251                for (int i = 0; i < subunits.getSubunitCount(); i++) {
252                        set.add(subunits.getChainIds().get(i) + "_" + subunits.getModelNumbers().get(i));
253                }
254                return set;
255        }
256
257        private void run() {
258                if (centers.size() > 0) {
259                        return;
260                }
261                calcOriginalCenters();
262                calcCentroid();
263                calcCenters();
264                calcMomentsOfIntertia();
265        }
266
267        private void calcOriginalCenters() {
268                for (Point3d[] trace: caCoords) {
269                        Point3d com = SuperPosition.centroid(trace);
270                        originalCenters.add(com);
271                }
272        }
273
274        private void calcCentroid() {
275                Point3d[] orig = originalCenters.toArray(new Point3d[originalCenters.size()]);
276                centroid = SuperPosition.centroid(orig);
277        }
278
279        private void calcCenters() {
280                for (Point3d p: originalCenters) {
281                        Point3d c = new Point3d(p);
282                        c.sub(centroid);
283                        centers.add(c);
284                        Vector3d v = new Vector3d(c);
285                        v.normalize();
286                        unitVectors.add(v);
287                }
288        }
289
290        public Point3d getLowerBound() {
291                Point3d lower = new Point3d();
292                for (Point3d p: centers) {
293                        if (p.x < lower.x) {
294                                lower.x = p.x;
295                        }
296                        if (p.y < lower.y) {
297                                lower.y = p.y;
298                        }
299                        if (p.z < lower.z) {
300                                lower.z = p.z;
301                        }
302                }
303                return lower;
304        }
305
306        public Point3d getUpperBound() {
307                Point3d upper = new Point3d();
308                for (Point3d p: centers) {
309                        if (p.x > upper.x) {
310                                upper.x = p.x;
311                        }
312                        if (p.y > upper.y) {
313                                upper.y = p.y;
314                        }
315                        if (p.z > upper.z) {
316                                upper.z = p.z;
317                        }
318                }
319                return upper;
320        }
321
322        private void calcMomentsOfIntertia() {
323                for (Point3d[] trace: caCoords) {
324                        for (Point3d p: trace) {
325                                momentsOfInertia.addPoint(p, 1.0f);
326                        }
327                }
328        }
329}