1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2006-10-15 17:34:01 -0500 (Sun, 15 Oct 2006) $
4  * $Revision: 5957 $
5  *
6  * Copyright (C) 2003-2005  Miguel, Jmol Development, www.jmol.org
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 package org.jmol.adapter.readers.cif;
26 
27 import java.util.Hashtable;
28 import java.util.Map;
29 
30 import javajs.util.Lst;
31 import javajs.util.M4;
32 import javajs.util.MessagePackReader;
33 import javajs.util.PT;
34 import javajs.util.SB;
35 
36 import org.jmol.adapter.smarter.Atom;
37 import org.jmol.adapter.smarter.Bond;
38 import org.jmol.adapter.smarter.Structure;
39 import javajs.util.BS;
40 import org.jmol.script.SV;
41 import org.jmol.util.Logger;
42 
43 /**
44  * JmolData RCSB MMTF (macromolecular transmission format) file reader
45  *
46  * see https://github.com/rcsb/mmtf/blob/master/spec.md
47  *
48  * full specification Version: v0.2+dev (as of 2016.08.08) is
49  * implemented,including:
50  *
51  * reading atoms, bonds, and DSSP 1.0 secondary structure
52  *
53  * load =1f88.mmtf filter "DSSP1"
54  *
55  * [Note that the filter "DSSP1" is required, since mmtf included DSSP 1.0
56  * calculations, while the standard for Jmol itself is DSSP 2.0. These two
57  * calculations differ in their treating of helix kinks as one (1.0) or two
58  * (2.0) helices.]
59  *
60  *
61  * reading space groups and unit cells, and using those as per other readers
62  *
63  * load =1crn.mmtf {1 1 1}
64  *
65  * reading bioassemblies (biomolecules) and applying all symmetry
66  * transformations
67  *
68  * load =1auy.mmtf FILTER "biomolecule 1;*.CA,*.P"
69  *
70  * reading both biomolecules and lattices, and loading course-grained using the
71  * filter "BYCHAIN" or "BYSYMOP"
72  *
73  * load =1auy.mmtf {2 2 1} filter "biomolecule 1;bychain";spacefill 30.0; color
74  * property symop
75  *
76  * Many thanks to the MMTF team at RCSB for assistance in this implementation.
77  *
78  * @author Bob Hanson hansonr@stolaf.edu
79  *
80  */
81 
82 public class MMTFReader extends MMCifReader {
83 
84   private boolean haveStructure;
85   private String pdbID;
86 
87 
88   @Override
addHeader()89   protected void addHeader() {
90     // no header for this type
91   }
92 
93   /**
94    * standard set up
95    *
96    * @param fullPath
97    * @param htParams
98    * @param reader
99    */
100   @Override
setup(String fullPath, Map<String, Object> htParams, Object reader)101   protected void setup(String fullPath, Map<String, Object> htParams,
102                        Object reader) {
103     isBinary = true;
104     isMMCIF = true;
105     iHaveFractionalCoordinates = false;
106     setupASCR(fullPath, htParams, reader);
107   }
108 
109   @Override
processBinaryDocument()110   protected void processBinaryDocument() throws Exception {
111 
112     // load xxx.mmtf filter "..." options
113 
114     // NODOUBLE -- standard PDB-like structures
115     // DSSP2 -- use Jmol's built-in DSSP 2.0, not file's DSSP 1.0
116 
117     boolean doDoubleBonds = (!isCourseGrained && !checkFilterKey("NODOUBLE"));
118     isDSSP1 = !checkFilterKey("DSSP2");
119     boolean mmtfImplementsDSSP2 = false; // so far!
120     applySymmetryToBonds = true;
121     map = (new MessagePackReader(binaryDoc, true)).readMap();
122     entities = (Object[]) map.get("entityList");
123     if (Logger.debugging) {
124       for (String s : map.keySet())
125         Logger.info(s);
126     }
127     asc.setInfo("noAutoBond", Boolean.TRUE);
128     Logger.info("MMTF version " + map.get("mmtfVersion"));
129     Logger.info("MMTF Producer " + map.get("mmtfProducer"));
130     String title = (String) map.get("title");
131     if (title != null)
132       appendLoadNote(title);
133     pdbID = (String) map.get("structureId");
134     if (pdbID == null)
135       pdbID = (String) map.get("pdbId");
136     fileAtomCount = ((Integer) map.get("numAtoms")).intValue();
137     int nBonds = ((Integer) map.get("numBonds")).intValue();
138 
139     groupCount = ((Integer) map.get("numGroups")).intValue();
140     groupModels = new int[groupCount]; // group model (file-based)
141     groupDSSP = new int[groupCount];   // group structure type (Jmol-based)
142     groupMap = new int[groupCount];    // file->jmol group index map
143 
144     int modelCount = ((Integer) map.get("numModels")).intValue();
145     appendLoadNote("id=" + pdbID + " numAtoms=" + fileAtomCount + " numBonds="
146         + nBonds + " numGroups=" + groupCount + " numModels=" + modelCount);
147     getMMTFAtoms(doDoubleBonds);
148     if (!isCourseGrained) {
149       int[] bo = (int[]) decode("bondOrderList");
150       int[] bi = (int[]) decode("bondAtomList");
151       addMMTFBonds(bo, bi, 0, doDoubleBonds, true);
152       if (isDSSP1 || mmtfImplementsDSSP2)
153         getStructure();
154     }
155     setMMTFSymmetry();
156     getMMTFBioAssembly();
157     setModelPDB(true);
158     if (Logger.debuggingHigh)
159       Logger.info(SV.getVariable(map).asString());
160   }
161 
162   @Override
applySymmetryAndSetTrajectory()163   public void applySymmetryAndSetTrajectory() throws Exception {
164     ac0 = ac;
165     super.applySymmetryAndSetTrajectory();
166     if (haveStructure)
167       addStructureSymmetry();
168   }
169 
170   //////////////////////////////// MMTF-Specific /////////////////////////
171 
172   private Map<String, Object> map; // input JSON-like map from MessagePack binary file
173   private int fileAtomCount;
174   private int opCount = 0;
175   private int[] groupModels, groupMap, groupDSSP, atomGroup;
176   private String[] labelAsymList; // created in getAtoms; used in getBioAssembly
177   private Atom[] atomMap; // necessary because some atoms may be deleted.
178   private Object[] entities;
179   private int groupCount;
180   private int ac0;
181   private BS[] bsStructures;
182   private int lastGroup;
183 
184 
185   // TODO  - also consider mapping group indices
186 
187   /**
188    * set up all atoms, including bonding, within a group
189    *
190    * @param doMulti
191    *        true to add double bonds
192    *
193    * @throws Exception
194    */
195   @SuppressWarnings("unchecked")
getMMTFAtoms(boolean doMulti)196   private void getMMTFAtoms(boolean doMulti) throws Exception {
197 
198     // chains
199     int[] chainsPerModel = (int[]) map.get("chainsPerModel");
200     int[] groupsPerChain = (int[]) map.get("groupsPerChain"); // note that this is label_asym, not auth_asym
201     labelAsymList = (String[]) decode("chainIdList"); // label_asym
202     String[] authAsymList = (String[]) decode("chainNameList"); // Auth_asym
203 
204     // groups
205     int[] groupTypeList = (int[]) decode("groupTypeList");
206     int[] groupIdList = (int[]) decode("groupIdList");
207     Object[] groupList = (Object[]) map.get("groupList");
208     char[] insCodes = (char[]) decode("insCodeList");
209     int[] atomId =    (int[]) decode("atomIdList");
210     boolean haveSerial = (atomId != null);
211     char[] altloc = (char[]) decode("altLocList"); // rldecode32
212     float[] occ = (float[]) decode("occupancyList");
213     float[] x = (float[]) decode("xCoordList");//getFloatsSplit("xCoord", 1000f);
214     float[] y = (float[]) decode("yCoordList");
215     float[] z = (float[]) decode("zCoordList");
216     float[] bf = (float[]) decode("bFactorList");
217     String[] nameList = (useAuthorChainID ? authAsymList : labelAsymList);
218     int iModel = -1;
219     int iChain = 0;
220     int nChain = 0;
221     int iGroup = 0;
222     int nGroup = 0;
223     int chainpt = 0;
224     int seqNo = 0;
225     int iatom = 0;
226     String chainID = "";
227     String authAsym = "", labelAsym = "";
228     char insCode = '\0';
229     atomMap = new Atom[fileAtomCount];
230     atomGroup = new int[fileAtomCount];
231     for (int j = 0, thisGroup = -1; j < groupCount; j++) {
232       if (++iGroup >= nGroup) {
233         chainID = nameList[chainpt];
234         authAsym = authAsymList[chainpt];
235         labelAsym = labelAsymList[chainpt];
236         nGroup = groupsPerChain[chainpt++];
237         iGroup = 0;
238         if (++iChain >= nChain) {
239           groupModels[j] = ++iModel;
240           nChain = chainsPerModel[iModel];
241           iChain = 0;
242           setModelPDB(true);
243           incrementModel(iModel + 1);
244           asc.setCurrentModelInfo("pdbID", pdbID);
245           nAtoms0 = asc.ac;
246           if (done)
247             return;
248         }
249       }
250       Map<String, Object> g = (Map<String, Object>) groupList[groupTypeList[j]];
251       String[] atomNameList = (String[]) g.get("atomNameList");
252       int len = atomNameList.length;
253       if (skipping) {
254         iatom += len;
255         continue;
256       }
257       int a0 = iatom;
258       if (insCodes != null)
259         insCode = insCodes[j];
260       seqNo = groupIdList[j];
261       String group3 = (String) g.get("groupName");
262       boolean isHetero = vwr.getJBR().isHetero(group3);
263       if (isHetero) {
264         // looking for CHEM_COMP_NAME here... "IRON/SULFUR CLUSTER" for SF4 in 1blu
265         String hetName = "" + g.get("chemCompType");
266         if (htHetero == null || !htHetero.containsKey(group3)) {
267 
268           // this is not ideal -- see 1a37.mmtf, where PSE is LSQRQRST(PSE)TPNVHM
269           // (actually that should be phosphoserine, SPE...)
270           // It is still listed as NON-POLYMER even though it is just a modified AA.
271 
272           if (entities != null && hetName.equals("NON-POLYMER"))
273             out: for (int i = entities.length; --i >= 0;) {
274               Map<String, Object> entity = (Map<String, Object>) entities[i];
275               int[] chainList = (int[]) entity.get("chainIndexList");
276               for (int k = chainList.length; --k >= 0;)
277                 if (chainList[k] == iChain) {
278                   hetName = "a component of the entity \"" + entity.get("description") + "\"";
279                   break out;
280                 }
281             }
282           addHetero(group3, hetName, false, true);
283         }
284       }
285       String[] elementList = (String[]) g.get("elementList");
286       boolean haveAtom = false;
287       for (int ia = 0, pt = 0; ia < len; ia++, iatom++) {
288         Atom a = new Atom();
289         a.isHetero = isHetero;
290         if (insCode != 0)
291           a.insertionCode = insCode;
292         setAtomCoordXYZ(a, x[iatom], y[iatom], z[iatom]);
293         a.elementSymbol = elementList[pt];
294         a.atomName = atomNameList[pt++];
295         if (seqNo >= 0)
296           maxSerial = Math.max(maxSerial, a.sequenceNumber = seqNo);
297         a.group3 = group3;
298         setChainID(a, chainID);
299         if (bf != null)
300           a.bfactor = bf[iatom];
301         if (altloc != null)
302           a.altLoc = altloc[iatom];
303         if (occ != null)
304           a.foccupancy = occ[iatom];
305         if (haveSerial)
306           a.atomSerial = atomId[iatom];
307         if (!filterAtom(a, -1) || !processSubclassAtom(a, labelAsym, authAsym))
308           continue;
309 
310         if (!haveAtom) {
311           thisGroup++;
312           haveAtom = true;
313         }
314         if (haveSerial) {
315           asc.addAtomWithMappedSerialNumber(a);
316         } else {
317           asc.addAtom(a);
318         }
319         atomMap[iatom] = a;
320         atomGroup[ac] = j;
321         groupMap[j] = lastGroup = thisGroup;
322         ac++;
323       }
324       if (!isCourseGrained) {
325         int[] bo = (int[]) g.get("bondOrderList");
326         int[] bi = (int[]) g.get("bondAtomList");
327         addMMTFBonds(bo, bi, a0, doMulti, false);
328       }
329     }
330     asc.setCurrentModelInfo("pdbID", pdbID);
331   }
332 
addMMTFBonds(int[] bo, int[] bi, int a0, boolean doMulti, boolean isInter)333   private void addMMTFBonds(int[] bo, int[] bi, int a0, boolean doMulti, boolean isInter) {
334     if (bi == null)
335       return;
336     doMulti &= (bo != null);
337     for (int bj = 0, pt = 0, nj = bi.length / 2; bj < nj; bj++) {
338       Atom a1 = atomMap[bi[pt++] + a0];
339       Atom a2 = atomMap[bi[pt++] + a0];
340       if (a1 != null && a2 != null) {
341         Bond bond = new Bond(a1.index, a2.index, doMulti ? bo[bj] : 1);
342         asc.addBond(bond);
343         if (Logger.debugging && isInter) {
344           Logger.info("inter-group (" + (a1.atomSetIndex + 1) + "." + a1.index + "/" + (a2.atomSetIndex + 1) + "." + a2.index + ") bond " + a1.group3 + a1.sequenceNumber + "." + a1.atomName
345               + " - " + a2.group3 + a2.sequenceNumber + "." + a2.atomName + " "
346               + bond.order);
347         }
348       }
349     }
350   }
351 
setMMTFSymmetry()352   private void setMMTFSymmetry() {
353     setSpaceGroupName((String) map.get("spaceGroup"));
354     float[] o = (float[]) map.get("unitCell");
355     if (o != null)
356       for (int i = 0; i < 6; i++)
357         setUnitCellItem(i, o[i]);
358   }
359 
360   @SuppressWarnings("unchecked")
getMMTFBioAssembly()361   private void getMMTFBioAssembly() {
362     Object[] o = (Object[]) map.get("bioAssemblyList");
363     if (o == null)
364       return;
365     if (vBiomolecules == null)
366       vBiomolecules = new Lst<Map<String, Object>>();
367 
368     for (int i = o.length; --i >= 0;) {
369       Map<String, Object> info = new Hashtable<String, Object>();
370       vBiomolecules.addLast(info);
371       int iMolecule = i + 1;
372       checkFilterAssembly("" + iMolecule, info);
373       info.put("name", "biomolecule " + iMolecule);
374       info.put("molecule", Integer.valueOf(iMolecule));
375       Lst<String> assemb = new Lst<String>();
376       Lst<String> ops = new Lst<String>();
377       info.put("biomts", new Lst<M4>());
378       info.put("chains", new Lst<String>());
379       info.put("assemblies", assemb);
380       info.put("operators", ops);
381       // need to add NCS here.
382       Map<String, Object> m = (Map<String, Object>) o[i];
383       Object[] tlist = (Object[]) m.get("transformList");
384       SB chlist = new SB();
385 
386       for (int j = 0, n = tlist.length; j < n; j++) {
387 
388         Map<String, Object> t = (Map<String, Object>) tlist[j];
389 
390         // for every transformation...
391 
392         chlist.setLength(0);
393 
394         // for compatibility with the mmCIF reader, we create
395         // string lists of the chains in the form $A$B$C...
396 
397         int[] chainList = (int[]) t.get("chainIndexList");
398         for (int k = 0, kn = chainList.length; k < kn; k++)
399           chlist.append("$").append(labelAsymList[chainList[k]]);
400         assemb.addLast(chlist.append("$").toString());
401 
402         // now save the 4x4 matrix transform for this operation
403 
404         String id = "" + (++opCount);
405         addMatrix(id, M4.newA16((float[]) t.get("matrix")), false);
406         ops.addLast(id);
407       }
408     }
409   }
410 
411   //  Code  Name
412   //  0*   pi helix
413   //  1   bend   (ignored)
414   //  2*   alpha helix
415   //  3*   extended (sheet)
416   //  4*   3-10 helix
417   //  5   bridge (ignored)
418   //  6*   turn
419   //  7   coil (ignored)
420   //  -1  undefined
421 
422   // 1F88: "secStructList": [7713371173311776617777666177444172222222222222222222222222222222166771222222222222222222262222222222617662222222222222222222222222222222222177111777722222222222222222221222261173333666633337717776666222222222226622222222222226611771777
423   // DSSP (Jmol):            ...EE....EE....TT.....TTT...GGG..HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH.TT...HHHHHHHHHHHHHHHHHHHTHHHHHHHHHHT..TTHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH..........HHHHHHHHHHHHHHHHHHH.HHHHT...EEEETTTTEEEE......TTTTHHHHHHHHHHHTTHHHHHHHHHHHHHTT........
424 
425   /**
426    * Get and translate the DSSP string from digit format
427    *
428    *        input data
429    */
getStructure()430   private void getStructure() {
431     int[] a = (int[]) decode("secStructList");
432     if (Logger.debugging)
433       Logger.info(PT.toJSON("secStructList", a));
434     bsStructures = new BS[] { new BS(), null, new BS(), new BS(),
435         new BS(), null, new BS() };
436     int lastGroup = -1;
437     for (int j = 0; j < a.length; j++) {
438       int type = a[j];
439       switch (type) {
440       case 0: // PI
441       case 2: // alpha
442       case 3: // sheet
443       case 4: // 3-10
444       case 6: // turn
445         int igroup = groupMap[j];
446         bsStructures[type].set(igroup);
447         groupDSSP[igroup] = type + 1;
448         lastGroup = j;
449       }
450     }
451 
452     int n = (isDSSP1 ? asc.iSet : groupModels[lastGroup]);
453     if (lastGroup >= 0) {
454       // a single structure takes care of everything
455       haveStructure = true;
456       asc.addStructure(new Structure(n, null, null, null, 0, 0, bsStructures));
457     }
458   }
459 
460   /**
461    * We must add groups to the proper bsStructure element
462    *
463    */
addStructureSymmetry()464   private void addStructureSymmetry() {
465     if (asc.ac == 0)
466       return;
467     Atom[] atoms = asc.atoms;
468     BS bsAtoms = asc.bsAtoms;
469 
470     // must point to groups here.
471 
472     int ptGroup = lastGroup;
473     int mygroup = -1;
474     for (int i = ac0, n = asc.ac; i < n; i++) {
475       if (bsAtoms == null || bsAtoms.get(i)) {
476         Atom a = atoms[i];
477         int igroup = atomGroup[a.atomSite];
478         if (igroup != mygroup) {
479           mygroup = igroup;
480           ptGroup++;
481         }
482         int dssp = groupDSSP[igroup];
483         if (dssp > 0) {
484           bsStructures[dssp - 1].set(ptGroup);
485         }
486       }
487     }
488   }
489 
490 
decode(String key)491   private Object decode(String key) {
492       return MessagePackReader.decode((byte[]) map.get(key));
493   }
494 }
495