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.quaternary;
023
024import org.biojava.nbio.structure.Calc;
025import org.biojava.nbio.structure.Chain;
026import org.biojava.nbio.structure.Structure;
027import org.biojava.nbio.structure.io.mmcif.model.PdbxStructAssembly;
028import org.biojava.nbio.structure.io.mmcif.model.PdbxStructAssemblyGen;
029import org.biojava.nbio.structure.io.mmcif.model.PdbxStructOperList;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import java.util.*;
034
035/**
036 * Reconstructs the quaternary structure of a protein from an asymmetric unit
037 *
038 * @author Peter Rose
039 * @author Andreas Prlic
040 * @author Jose Duarte
041 *
042 */
043public class BiologicalAssemblyBuilder {
044
045        private static final Logger logger = LoggerFactory.getLogger(BiologicalAssemblyBuilder.class);
046
047        private OperatorResolver operatorResolver;
048        private List<PdbxStructAssemblyGen> psags;
049
050        private List<BiologicalAssemblyTransformation> modelTransformations;
051
052        private List<String> modelIndex = new ArrayList<String>();
053
054        public BiologicalAssemblyBuilder(){
055                init();
056        }
057
058        /**
059         * Builds a Structure object containing the quaternary structure built from given asymUnit and transformations,
060         * by adding symmetry partners as new models.
061         * The output Structure will be different depending on the multiModel parameter:
062         * <li>
063         * the symmetry-expanded chains are added as new models, one per transformId. All original models but 
064         * the first one are discarded.
065         * </li>
066         * <li>
067         * as original with symmetry-expanded chains added with renamed chain ids and names (in the form 
068         * originalAsymId_transformId and originalAuthId_transformId)
069         * </li>
070         * @param asymUnit
071         * @param transformations
072         * @param useAsymIds if true use {@link Chain#getId()} to match the ids in the BiologicalAssemblyTransformation (needed if data read from mmCIF), 
073         * if false use {@link Chain#getName()} for the chain matching (needed if data read from PDB).
074         * @param multiModel if true the output Structure will be a multi-model one with one transformId per model, 
075         * if false the outputStructure will be as the original with added chains with renamed asymIds (in the form originalAsymId_transformId and originalAuthId_transformId). 
076         * @return
077         */
078        public Structure rebuildQuaternaryStructure(Structure asymUnit, List<BiologicalAssemblyTransformation> transformations, boolean useAsymIds, boolean multiModel) {
079                
080                // ensure that new chains are build in the same order as they appear in the asymmetric unit
081                orderTransformationsByChainId(asymUnit, transformations);
082
083                Structure s = asymUnit.clone();
084                
085
086                // this resets all models (not only the first one): this is important for NMR (multi-model)
087                // like that we can be sure we start with an empty structures and we add models or chains to it
088                s.resetModels();
089
090                for (BiologicalAssemblyTransformation transformation : transformations){
091
092                        List<Chain> chainsToTransform = new ArrayList<>();
093                        
094                        // note: for NMR structures (or any multi-model) we use the first model only and throw away the rest
095                        if (useAsymIds) {
096                                Chain c = asymUnit.getChain(transformation.getChainId());
097                                chainsToTransform.add(c);
098                        } else {
099                                Chain polyC = asymUnit.getPolyChainByPDB(transformation.getChainId());
100                                List<Chain> nonPolyCs = asymUnit.getNonPolyChainsByPDB(transformation.getChainId());
101                                Chain waterC = asymUnit.getWaterChainByPDB(transformation.getChainId());
102                                if (polyC!=null) 
103                                        chainsToTransform.add(polyC);
104                                if (!nonPolyCs.isEmpty()) 
105                                        chainsToTransform.addAll(nonPolyCs);
106                                if (waterC!=null) 
107                                        chainsToTransform.add(waterC);
108                        }
109                        
110                        for (Chain c: chainsToTransform) {
111
112                                Chain chain = (Chain)c.clone();
113                                
114                                Calc.transform(chain, transformation.getTransformationMatrix());
115
116                                String transformId = transformation.getId();
117
118                                // note that the Structure.addChain/Structure.addModel methods set the parent reference to the new Structure
119                                
120                                // TODO set entities properly in the new structures! at the moment they are a mess... - JD 2016-05-19
121                                
122                                if (multiModel) 
123                                        addChainMultiModel(s, chain, transformId);
124                                else 
125                                        addChainFlattened(s, chain, transformId);
126
127                        }
128                }
129
130                s.setBiologicalAssembly(true);
131                return s;
132        }
133
134        /**
135         * Orders model transformations by chain ids in the same order as in the asymmetric unit
136         * @param asymUnit
137         * @param transformations
138         */
139        private void orderTransformationsByChainId(Structure asymUnit, List<BiologicalAssemblyTransformation> transformations) {
140                final List<String> chainIds = getChainIds(asymUnit);
141                Collections.sort(transformations, new Comparator<BiologicalAssemblyTransformation>() {
142                        @Override
143                        public int compare(BiologicalAssemblyTransformation t1, BiologicalAssemblyTransformation t2) {
144                                // set sort order only if the two ids are identical
145                                if (t1.getId().equals(t2.getId())) {
146                                         return chainIds.indexOf(t1.getChainId()) - chainIds.indexOf(t2.getChainId());
147                                }
148                            return 0;
149                    }
150                });
151        }
152
153        /**
154         * Returns a list of chain ids in the order they are specified in the ATOM
155         * records in the asymmetric unit
156         * @param asymUnit
157         * @return
158         */
159        private List<String> getChainIds(Structure asymUnit) {
160                List<String> chainIds = new ArrayList<String>();
161                for ( Chain c : asymUnit.getChains()){
162                        String intChainID = c.getId();
163                        chainIds.add(intChainID);
164                }
165                return chainIds;
166        }
167
168        /**
169         * Adds a chain to the given structure to form a biological assembly,
170         * adding the symmetry expanded chains as new models per transformId.
171         * @param s
172         * @param newChain
173         * @param transformId
174         */
175        private void addChainMultiModel(Structure s, Chain newChain, String transformId) {
176
177                // multi-model bioassembly
178
179                if ( modelIndex.size() == 0)
180                        modelIndex.add("PLACEHOLDER FOR ASYM UNIT");
181
182                int modelCount = modelIndex.indexOf(transformId);
183                if ( modelCount == -1)  {
184                        modelIndex.add(transformId);
185                        modelCount = modelIndex.indexOf(transformId);
186                }
187
188                if (modelCount == 0) {
189                        s.addChain(newChain);
190                } else if (modelCount > s.nrModels()) {
191                        List<Chain> newModel = new ArrayList<Chain>();
192                        newModel.add(newChain);
193                        s.addModel(newModel);
194                } else {
195                        s.addChain(newChain, modelCount-1);
196                }
197
198        }
199        
200        /**
201         * Adds a chain to the given structure to form a biological assembly,
202         * adding the symmetry-expanded chains as new chains with renamed 
203         * chain ids and names (in the form originalAsymId_transformId and originalAuthId_transformId).
204         * @param s
205         * @param newChain
206         * @param transformId
207         */
208        private void addChainFlattened(Structure s, Chain newChain, String transformId) {
209                newChain.setId(newChain.getId()+"_"+transformId);
210                newChain.setName(newChain.getName()+"_"+transformId);
211                s.addChain(newChain);           
212        }
213
214        /**
215         * Returns a list of transformation matrices for the generation of a macromolecular
216         * assembly for the specified assembly Id.
217         *
218         * @param assemblyId Id of the macromolecular assembly to be generated
219         * @return list of transformation matrices to generate macromolecular assembly
220         */
221        public ArrayList<BiologicalAssemblyTransformation> getBioUnitTransformationList(PdbxStructAssembly psa, List<PdbxStructAssemblyGen> psags, List<PdbxStructOperList> operators) {
222                //System.out.println("Rebuilding " + psa.getDetails() + " | " + psa.getOligomeric_details() + " | " + psa.getOligomeric_count());
223                //System.out.println(psag);
224                init();
225                this.psags = psags;
226
227                //psa.getId();
228
229                for (PdbxStructOperList oper: operators){
230                        BiologicalAssemblyTransformation transform = new BiologicalAssemblyTransformation();
231                        transform.setId(oper.getId());
232                        transform.setRotationMatrix(oper.getMatrix().getArray());
233                        transform.setTranslation(oper.getVector());
234//                      transform.setTransformationMatrix(oper.getMatrix(), oper.getVector());
235                        modelTransformations.add(transform);
236                }
237
238                ArrayList<BiologicalAssemblyTransformation> transformations = getBioUnitTransformationsListUnaryOperators(psa.getId());
239                transformations.addAll(getBioUnitTransformationsListBinaryOperators(psa.getId()));
240                transformations.trimToSize();
241                return transformations;
242        }
243
244
245        private ArrayList<BiologicalAssemblyTransformation> getBioUnitTransformationsListBinaryOperators(String assemblyId) {
246
247                ArrayList<BiologicalAssemblyTransformation> transformations = new ArrayList<BiologicalAssemblyTransformation>();
248
249                List<OrderedPair<String>> operators = operatorResolver.getBinaryOperators();
250
251
252                for ( PdbxStructAssemblyGen psag : psags){
253                        if ( psag.getAssembly_id().equals(assemblyId)) {
254
255                                List<String>asymIds= Arrays.asList(psag.getAsym_id_list().split(","));
256
257                                operatorResolver.parseOperatorExpressionString(psag.getOper_expression());
258
259                                // apply binary operators to the specified chains
260                                // Example 1M4X: generates all products of transformation matrices (1-60)(61-88)
261                                for (String chainId : asymIds) {
262
263                                        int modelNumber = 1;
264                                        for (OrderedPair<String> operator : operators) {
265                                                BiologicalAssemblyTransformation original1 = getModelTransformationMatrix(operator.getElement1());
266                                                BiologicalAssemblyTransformation original2 = getModelTransformationMatrix(operator.getElement2());
267                        //                      ModelTransformationMatrix transform = ModelTransformationMatrix.multiply4square_x_4square2(original1, original2);
268                                                BiologicalAssemblyTransformation transform = BiologicalAssemblyTransformation.combine(original1, original2);
269                                                transform.setChainId(chainId);
270                                //              transform.setId(original1.getId() + "x" + original2.getId());
271                                                transform.setId(String.valueOf(modelNumber));
272                                                transformations.add(transform);
273                                                modelNumber++;
274                                        }
275                                }
276                        }
277
278                }
279
280                return transformations;
281        }
282
283        private BiologicalAssemblyTransformation getModelTransformationMatrix(String operator) {
284                for (BiologicalAssemblyTransformation transform: modelTransformations) {
285                        if (transform.getId().equals(operator)) {
286                                return transform;
287                        }
288                }
289                logger.error("Could not find modelTransformationmatrix for " + operator);
290                return new BiologicalAssemblyTransformation();
291        }
292
293        private ArrayList<BiologicalAssemblyTransformation> getBioUnitTransformationsListUnaryOperators(String assemblyId) {
294                ArrayList<BiologicalAssemblyTransformation> transformations = new ArrayList<BiologicalAssemblyTransformation>();
295
296
297                for ( PdbxStructAssemblyGen psag : psags){
298                        if ( psag.getAssembly_id().equals(assemblyId)) {
299
300                                operatorResolver.parseOperatorExpressionString(psag.getOper_expression());
301                                List<String> operators = operatorResolver.getUnaryOperators();
302
303                                List<String>asymIds= Arrays.asList(psag.getAsym_id_list().split(","));
304
305                                // apply unary operators to the specified chains
306                                for (String chainId : asymIds) {
307                                        for (String operator : operators) {
308
309                                                BiologicalAssemblyTransformation original = getModelTransformationMatrix(operator);
310                                                BiologicalAssemblyTransformation transform = new BiologicalAssemblyTransformation(original);
311                                                transform.setChainId(chainId);
312                                                transform.setId(operator);
313                                                transformations.add(transform);
314                                        }
315                                }
316                        }
317                }
318
319                return transformations;
320        }
321
322        private void init(){
323                operatorResolver= new OperatorResolver();
324                modelTransformations = new ArrayList<BiologicalAssemblyTransformation>(1);
325        }
326}