1 package org.jmol.adapter.readers.cif;
2 
3 import java.util.Hashtable;
4 import java.util.Map;
5 
6 import org.jmol.adapter.readers.cif.CifReader.Parser;
7 import org.jmol.adapter.smarter.Atom;
8 import org.jmol.adapter.smarter.Bond;
9 import org.jmol.api.JmolAdapter;
10 import org.jmol.api.SymmetryInterface;
11 import org.jmol.symmetry.SymmetryOperation;
12 import org.jmol.util.BSUtil;
13 import org.jmol.util.Edge;
14 import org.jmol.util.JmolMolecule;
15 
16 import javajs.api.GenericCifDataParser;
17 import javajs.util.BS;
18 import javajs.util.Lst;
19 import javajs.util.M4;
20 import javajs.util.P3;
21 import javajs.util.T3;
22 
23 /**
24  * see https://github.com/COMCIFS/TopoCif
25  *
26  * Basic idea:
27  *
28  * We have TLinks, TNodes, and TAtoms
29  *
30  * TLinks each have two TNodes and may also be associated with bridging TAtom
31  * sets.
32  *
33  * TNode extends TAtom and may also maintain a list of TAtoms.
34  *
35  * TAtoms extend Atom and may have symmetry aspects.
36  *
37  *
38  *
39  *
40  *
41  *
42  * @author Bob Hanson hansonr@stolaf.edu 2020.11.17 2021.05.07
43  *
44  */
45 public class TopoCifParser implements Parser {
46 
47   final static int TOPOL_LINK = 0x1000000;
48   final static int TOPOL_GROUP = 0x2000000;
49   final static int TOPOL_NODE = 0x4000000;
50 
51   /**
52    * types set by filter TOPOSE_TYPES in the format of one or more of {v, vw,
53    * hb} separated by "+"; default is v+hb
54    */
55   //  private final static String ` = "+v+hb+w+";
56   public static final int LINK_TYPE_GENERIC_LINK = 0;
57   public static final int LINK_TYPE_SINGLE = 1;
58   public static final int LINK_TYPE_DOUBLE = 2;
59   public static final int LINK_TYPE_TRIPLE = 3;
60   public static final int LINK_TYPE_QUADRUPLE = 4;
61   public static final int LINK_TYPE_QUINTUPLE = 5;
62   public static final int LINK_TYPE_SEXTUPLE = 6;
63   public static final int LINK_TYPE_SEPTUPLE = 7;
64   public static final int LINK_TYPE_OCTUPLE = 8;
65   public static final int LINK_TYPE_AROM = 9;
66   public static final int LINK_TYPE_POLY = 0xA;
67   public static final int LINK_TYPE_DELO = 0xB;
68   public static final int LINK_TYPE_PI = 0xC;
69   public static final int LINK_TYPE_HBOND = 0xD;
70   public static final int LINK_TYPE_VDW = 0xE;
71   public static final int LINK_TYPE_OTHER = 0xF; // Special bond
72 
73   // officially v pi hb vw sb . (no bond)
74   public static String linkTypes = "?  "
75       + "SIN" + "DOU" + "TRI" + "QUA" + "QUI" + "SEX" + "SEP" + "OCT" // 1-8
76       + "ARO" + "POL" + "DEL" // 9-11
77       + "PI " + "HBO" + "VDW"; //12-14
78 
getBondType(String type, int order)79   static int getBondType(String type, int order) {
80     if (type == null)
81       return LINK_TYPE_GENERIC_LINK;
82     type = type.toUpperCase();
83     if (type.equals("V"))
84       return (order == 0 ? LINK_TYPE_SINGLE : order);
85     if (type.equals("sb"))
86       type = "?";
87     switch (type.charAt(0)) {
88     case 'V':
89       return LINK_TYPE_VDW;
90     }
91     if (type.length() > 3)
92       type = type.substring(0, 3);
93     // defaults to SINGLE
94     return Math.max(1, linkTypes.indexOf(type) / 3);
95   }
96 
97   public static final int LINK_TYPE_BITS = 4;
98 
99   static double ERROR_TOLERANCE = 0.001;
100 
101   /**
102    * reader will be null if filter includes TOPOS_IGNORE
103    */
104   CifReader reader;
105 
106   /**
107    * list of TOPOL_ATOM loop data
108    */
109   Lst<TAtom> atoms = new Lst<TAtom>();
110 
111   /**
112    * list of TOPOL_NODE loop data
113    */
114   Lst<TNode> nodes = new Lst<TNode>();
115 
116   /**
117    * list of TOPOL_LINK loop data
118    */
119   Lst<TLink> links = new Lst<TLink>();
120 
121   /**
122    * list of TOPOL_NET loop or single data item data
123    */
124   Lst<TNet> nets = new Lst<TNet>();
125 
126   /**
127    * storage for a single net from a non-looped data item
128    */
129   TNet singleNet;
130 
131   int netCount;
132   int linkCount;
133   int atomCount;
134 
135   T3 temp1 = new P3(), temp2 = new P3();
136 
137   private int ac0 = -1, bc0;
138 
139   private GenericCifDataParser cifParser;
140 
141   /**
142    * and indictor that we should abort, and why
143    */
144   String failed;
145 
146   /**
147    * symmetry operations for this space group
148    *
149    */
150   M4[] ops;
151 
152   /**
153    * base atom index to be added to any atom bitsets
154    */
155   int i0;
156 
157   /**
158    * base bond index to be added to any bond bitsets
159    */
160   int b0;
161   private String allowedTypes;
162 
163   String netNotes = "";
164   private SymmetryInterface sym;
165   String selectedNet;
166 
167   // CifParser supports up to 100 fields
168   final private static String[] topolFields = {
169       /*0*/"_topol_net_id",
170       /*1*/"_topol_net_label",
171       /*2*/"_topol_net_special_details",
172       /*3*/"_topol_link_id",
173       /*4*/"_topol_link_net_id",
174       /*5*/"_topol_link_node_id_1",
175       /*6*/"_topol_link_node_id_2",
176       /*7*/"_topol_link_symop_id_1",
177       /*8*/"_topol_link_translation_1",
178       /*9*/"_topol_link_translation_1_x",
179       /*10*/"_topol_link_translation_1_y",
180       /*11*/"_topol_link_translation_1_z",
181       /*12*/"_topol_link_symop_id_2",
182       /*13*/"_topol_link_translation_2",
183       /*14*/"_topol_link_translation_2_x",
184       /*15*/"_topol_link_translation_2_y",
185       /*16*/"_topol_link_translation_2_z",
186       /*17*/"_topol_link_distance",
187       /*18*/"_topol_link_type",
188       /*19*/"_topol_link_multiplicity",
189       /*20*/"_topol_link_voronoi_solidangle",
190       /*21*/"_topol_link_order",
191       /*22*/"_topol_node_id",
192       /*23*/"_topol_node_net_id",
193       /*24*/"_topol_node_label",
194       /*25*/"_topol_node_symop_id",
195       /*26*/"_topol_node_translation",
196       /*27*/"_topol_node_translation_x",
197       /*28*/"_topol_node_translation_y",
198       /*29*/"_topol_node_translation_z",
199       /*30*/"_topol_node_fract_x",
200       /*31*/"_topol_node_fract_y",
201       /*32*/"_topol_node_fract_z",
202       /*33*/"_topol_atom_id",
203       /*34*/"_topol_atom_atom_label",
204       /*35*/"_topol_atom_node_id",
205       /*36*/"_topol_atom_link_id",
206       /*37*/"_topol_atom_symop_id",
207       /*38*/"_topol_atom_translation",
208       /*39*/"_topol_atom_translation_x",
209       /*40*/"_topol_atom_translation_y",
210       /*41*/"_topol_atom_translation_z",
211       /*42*/"_topol_atom_fract_x",
212       /*43*/"_topol_atom_fract_y",
213       /*44*/"_topol_atom_fract_z",
214       /*45*/"_topol_atom_element_symbol",
215       /*46*/"_topol_link_site_symmetry_symop_1",
216       /*47*/"_topol_link_site_symmetry_translation_1_x",
217       /*48*/"_topol_link_site_symmetry_translation_1_y",
218       /*49*/"_topol_link_site_symmetry_translation_1_z",
219       /*50*/"_topol_link_site_symmetry_symop_2",
220       /*51*/"_topol_link_site_symmetry_translation_2_x",
221       /*52*/"_topol_link_site_symmetry_translation_2_y",
222       /*53*/"_topol_link_site_symmetry_translation_2_z",
223       /*54*/"_topol_link_site_symmetry_translation_1",
224       /*55*/"_topol_link_site_symmetry_translation_2",
225       /*56*/"_topol_link_node_label_1",
226       /*57*/"_topol_link_node_label_2",
227       /*58*/"_topol_link_atom_label_1",
228       /*59*/"_topol_link_atom_label_2",
229   };
230 
231   private final static byte topol_net_id = 0;
232   private final static byte topol_net_label = 1;
233   private final static byte topol_net_special_details = 2;
234   private final static byte topol_link_id = 3;
235   private final static byte topol_link_net_id = 4;
236   private final static byte topol_link_node_id_1 = 5;
237   private final static byte topol_link_node_id_2 = 6;
238   private final static byte topol_link_symop_id_1 = 7;
239   private final static byte topol_link_translation_1 = 8;
240   private final static byte topol_link_translation_1_x = 9;
241   private final static byte topol_link_translation_1_y = 10;
242   private final static byte topol_link_translation_1_z = 11;
243   private final static byte topol_link_symop_id_2 = 12;
244   private final static byte topol_link_translation_2 = 13;
245   private final static byte topol_link_translation_2_x = 14;
246   private final static byte topol_link_translation_2_y = 15;
247   private final static byte topol_link_translation_2_z = 16;
248   private final static byte topol_link_distance = 17;
249   private final static byte topol_link_type = 18;
250   private final static byte topol_link_multiplicity = 19;
251   private final static byte topol_link_voronoi_solidangle = 20;
252   private final static byte topol_link_order = 21;
253   private final static byte topol_node_id = 22;
254   private final static byte topol_node_net_id = 23;
255   private final static byte topol_node_label = 24;
256   private final static byte topol_node_symop_id = 25;
257   private final static byte topol_node_translation = 26;
258   private final static byte topol_node_translation_x = 27;
259   private final static byte topol_node_translation_y = 28;
260   private final static byte topol_node_translation_z = 29;
261   private final static byte topol_node_fract_x = 30;
262   private final static byte topol_node_fract_y = 31;
263   private final static byte topol_node_fract_z = 32;
264   private final static byte topol_atom_id = 33;
265   private final static byte topol_atom_atom_label = 34;
266   private final static byte topol_atom_node_id = 35;
267   private final static byte topol_atom_link_id = 36;
268   private final static byte topol_atom_symop_id = 37;
269   private final static byte topol_atom_translation = 38;
270   private final static byte topol_atom_translation_x = 39;
271   private final static byte topol_atom_translation_y = 40;
272   private final static byte topol_atom_translation_z = 41;
273   private final static byte topol_atom_fract_x = 42;
274   private final static byte topol_atom_fract_y = 43;
275   private final static byte topol_atom_fract_z = 44;
276   private final static byte topol_atom_element_symbol = 45;
277   private final static byte topol_link_site_symmetry_symop_1_DEPRECATED = 46;
278   private final static byte topol_link_site_symmetry_translation_1_x_DEPRECATED = 47;
279   private final static byte topol_link_site_symmetry_translation_1_y_DEPRECATED = 48;
280   private final static byte topol_link_site_symmetry_translation_1_z_DEPRECATED = 49;
281   private final static byte topol_link_site_symmetry_symop_2_DEPRECATED = 50;
282   private final static byte topol_link_site_symmetry_translation_2_x_DEPRECATED = 51;
283   private final static byte topol_link_site_symmetry_translation_2_y_DEPRECATED = 52;
284   private final static byte topol_link_site_symmetry_translation_2_z_DEPRECATED = 53;
285   private final static byte topol_link_site_symmetry_translation_1_DEPRECATED = 54;
286   private final static byte topol_link_site_symmetry_translation_2_DEPRECATED = 55;
287   private final static byte topol_link_node_label_1_DEPRECATED = 56;
288   private final static byte topol_link_node_label_2_DEPRECATED = 57;
289 
TopoCifParser()290   public TopoCifParser() {
291   }
292 
293   /**
294    * filter "TOPOS_TYPES=hb" will only load hydrogen bonds; options include v,
295    * vw, and hb
296    */
297   @Override
setReader(CifReader reader)298   public TopoCifParser setReader(CifReader reader) {
299     if (!reader.checkFilterKey("TOPOL")) {
300       reader.appendLoadNote(
301           "This file has Topology analysis records.\nUse LOAD \"\" {1 1 1} FILTER \"TOPOL\"  to load the topology.");
302       return this;
303     }
304     this.reader = reader;
305     String net = reader.getFilter("TOPOLNET=");
306     selectedNet = net;
307     String types = reader.getFilter("TOPOS_TYPES=");
308     if (types == null)
309       types = reader.getFilter("TOPOS_TYPE=");
310     if (types != null && types.length() > 0) {
311       types = "+" + types.toLowerCase() + "+";
312       allowedTypes = types;
313     }
314     //    reader.asc.setNoAutoBond();
315     i0 = reader.baseAtomIndex;
316     b0 = reader.baseBondIndex;
317     return this;
318   }
319 
320   /**
321    * process _topol_node.id 1
322    *
323    */
324   @Override
ProcessRecord(String key, String data)325   public void ProcessRecord(String key, String data) throws Exception {
326     if (reader == null || failed != null) {
327       return;
328     }
329     int pt = key.indexOf(".");
330     if (pt < 0) {
331       // _topol_*_ --> _topol_*.
332       pt = key.indexOf('_',key.indexOf('_',1) + 1);
333       if (pt < 0)
334         return;
335       key = key.substring(0, pt) + '.' + key.substring(pt + 1);
336     }
337     processBlock(key);
338   }
339 
340   @Override
processBlock(String key)341   public boolean processBlock(String key) throws Exception {
342     if (reader == null || failed != null) {
343       return false;
344     }
345     if (ac0 < 0) {
346       ac0 = reader.asc.ac;
347       bc0 = reader.asc.bondCount;
348     }
349     if (reader.ucItems != null) {
350       reader.allow_a_len_1 = true;
351       for (int i = 0; i < 6; i++)
352         reader.setUnitCellItem(i, reader.ucItems[i]);
353     }
354     reader.parseLoopParameters(topolFields);
355     cifParser = reader.cifParser;
356     if (key.startsWith("_topol_net")) {
357       processNets();
358     } else if (key.startsWith("_topol_link")) {
359       processLinks();
360     } else if (key.startsWith("_topol_node")) {
361       processNodes();
362     } else if (key.startsWith("_topol_atom")) {
363       processAtoms();
364     } else {
365       return false;
366     }
367     return true;
368   }
369 
370   /**
371    * Process all nets. Note that the nets list is self-populating with a "Net1"
372    * value if there is no TOPOL_NET section.
373    *
374    * @throws Exception
375    */
processNets()376   private void processNets() throws Exception {
377     while (cifParser.getData()) {
378       String id = getDataValue(topol_net_id);
379       String netLabel = getDataValue(topol_net_label);
380       if (id == null)
381         id = "" + (netCount + 1);
382       TNet net = getNetFor(id, netLabel, true);
383       net.specialDetails = getDataValue(topol_net_special_details);
384       net.line = reader.line;
385     }
386   }
387 
processLinks()388   private void processLinks() throws Exception {
389     while (cifParser.getData()) {
390       String t = getDataValue(topol_link_type);
391       String type = (t == null ? null : t.toLowerCase());
392       if (allowedTypes != null
393           && (type == null || allowedTypes.indexOf("+" + type + "+") < 0))
394         continue;
395       TLink link = new TLink();
396       link.type = type;
397       int[] t1 = new int[3];
398       int[] t2 = new int[3];
399       int n = cifParser.getColumnCount();
400       for (int i = 0; i < n; ++i) {
401         int p = reader.fieldProperty(i);
402         String field = reader.field;
403         switch (p) {
404         case topol_link_id:
405           link.id = field;
406           break;
407         case topol_link_net_id:
408           link.netID = field;
409           break;
410         case topol_link_node_id_1:
411           link.nodeIds[0] = field;
412           break;
413         case topol_link_node_id_2:
414           link.nodeIds[1] = field;
415           break;
416         case topol_link_node_label_1_DEPRECATED: // legacy
417           link.nodeLabels[0] = field;
418           break;
419         case topol_link_node_label_2_DEPRECATED: // legacy
420           link.nodeLabels[1] = field;
421           break;
422         case topol_link_site_symmetry_symop_1_DEPRECATED:
423         case topol_link_symop_id_1:
424           link.symops[0] = getInt(field) - 1;
425           break;
426         case topol_link_site_symmetry_symop_2_DEPRECATED:
427         case topol_link_symop_id_2:
428           link.symops[1] = getInt(field) - 1;
429           break;
430         case topol_link_order:
431           link.topoOrder = getInt(field);
432           break;
433         case topol_link_site_symmetry_translation_1_DEPRECATED:
434         case topol_link_site_symmetry_translation_1_x_DEPRECATED:
435         case topol_link_site_symmetry_translation_1_y_DEPRECATED:
436         case topol_link_site_symmetry_translation_1_z_DEPRECATED:
437         case topol_link_translation_1:
438         case topol_link_translation_1_x:
439         case topol_link_translation_1_y:
440         case topol_link_translation_1_z:
441           t1 = processTranslation(p, t1, field);
442           break;
443         case topol_link_site_symmetry_translation_2_DEPRECATED:
444         case topol_link_site_symmetry_translation_2_x_DEPRECATED:
445         case topol_link_site_symmetry_translation_2_y_DEPRECATED:
446         case topol_link_site_symmetry_translation_2_z_DEPRECATED:
447         case topol_link_translation_2:
448         case topol_link_translation_2_x:
449         case topol_link_translation_2_y:
450         case topol_link_translation_2_z:
451           t2 = processTranslation(p, t2, field);
452           break;
453         case topol_link_distance:
454           link.cartesianDistance = getFloat(field);
455           break;
456         case topol_link_multiplicity:
457           link.multiplicity = getInt(field);
458           break;
459         case topol_link_voronoi_solidangle:
460           link.voronoiAngle = getFloat(field);
461         }
462       }
463       if (!link.setLink(t1, t2, reader.line)) {
464         failed = "invalid link! " + link;
465         return;
466       }
467       links.addLast(link);
468     }
469   }
470 
processNodes()471   private void processNodes() throws Exception {
472     while (cifParser.getData()) {
473       TNode node = new TNode();
474       int[] t = new int[3];
475       int n = cifParser.getColumnCount();
476       for (int i = 0; i < n; ++i) {
477         int p = reader.fieldProperty(i);
478         String field = reader.field;
479         switch (p) {
480         case topol_node_id:
481           node.id = field;
482           break;
483         case topol_node_label:
484           node.label = field;
485           break;
486         case topol_node_net_id:
487           node.netID = field;
488           break;
489         case topol_node_symop_id:
490           node.symop = getInt(field) - 1;
491           break;
492         case topol_node_translation:
493         case topol_node_translation_x:
494         case topol_node_translation_y:
495         case topol_node_translation_z:
496           t = processTranslation(p, t, field);
497           break;
498         case topol_node_fract_x:
499           node.x = getFloat(field);
500           break;
501         case topol_node_fract_y:
502           node.y = getFloat(field);
503           break;
504         case topol_node_fract_z:
505           node.z = getFloat(field);
506           break;
507         }
508       }
509       if (node.setNode(t, reader.line))
510         nodes.addLast(node);
511     }
512   }
513 
processAtoms()514   private void processAtoms() throws Exception {
515     while (cifParser.getData()) {
516       TAtom atom = new TAtom();
517       int[] t = new int[3];
518       int n = cifParser.getColumnCount();
519       for (int i = 0; i < n; ++i) {
520         int p = reader.fieldProperty(i);
521         String field = reader.field;
522         switch (p) {
523         case topol_atom_id:
524           atom.id = field;
525           break;
526         case topol_atom_atom_label:
527           atom.atomLabel = field;
528           break;
529         case topol_atom_node_id:
530           atom.nodeID = field;
531           break;
532         case topol_atom_link_id:
533           atom.linkID = field;
534           break;
535         case topol_atom_symop_id:
536           atom.symop = getInt(field) - 1;
537           break;
538         case topol_atom_translation:
539         case topol_atom_translation_x:
540         case topol_atom_translation_y:
541         case topol_atom_translation_z:
542           t = processTranslation(p, t, field);
543           break;
544         case topol_atom_fract_x:
545           atom.x = getFloat(field);
546           break;
547         case topol_atom_fract_y:
548           atom.y = getFloat(field);
549           break;
550         case topol_atom_fract_z:
551           atom.z = getFloat(field);
552           break;
553         case topol_atom_element_symbol:
554           atom.elementSymbol = field;
555           break;
556         }
557       }
558       if (atom.setAtom(t, reader.line))
559         atoms.addLast(atom);
560     }
561   }
562 
processTranslation(int p, int[] t, String field)563   private int[] processTranslation(int p, int[] t, String field) {
564     switch (p) {
565     case topol_link_site_symmetry_translation_1_DEPRECATED:
566     case topol_link_site_symmetry_translation_2_DEPRECATED:
567     case topol_link_translation_1:
568     case topol_link_translation_2:
569     case topol_node_translation:
570     case topol_atom_translation:
571       t = Cif2DataParser.getIntArrayFromStringList(field, 3);
572       break;
573     case topol_link_site_symmetry_translation_1_x_DEPRECATED:
574     case topol_link_site_symmetry_translation_2_x_DEPRECATED:
575     case topol_link_translation_1_x:
576     case topol_link_translation_2_x:
577     case topol_node_translation_x:
578     case topol_atom_translation_x:
579       t[0] = getInt(field);
580       break;
581     case topol_link_site_symmetry_translation_1_y_DEPRECATED:
582     case topol_link_site_symmetry_translation_2_y_DEPRECATED:
583     case topol_link_translation_1_y:
584     case topol_link_translation_2_y:
585     case topol_node_translation_y:
586     case topol_atom_translation_y:
587       t[1] = getInt(field);
588       break;
589     case topol_link_site_symmetry_translation_1_z_DEPRECATED:
590     case topol_link_site_symmetry_translation_2_z_DEPRECATED:
591     case topol_link_translation_1_z:
592     case topol_link_translation_2_z:
593     case topol_node_translation_z:
594     case topol_atom_translation_z:
595       t[2] = getInt(field);
596       break;
597     }
598     return t;
599   }
600 
601   /**
602    * PRIOR to symmetry application, process all internal symop/translation
603    * aspects.
604    */
605   @Override
finalizeReader()606   public boolean finalizeReader() throws Exception {
607     // opportunity to handle anything prior to applying symmetry
608     if (reader == null || reader.symops == null)
609       return false;
610     cifParser = null;
611     reader.applySymmetryToBonds = true;
612     // finalize all topol_atom symmetries
613     Lst<String> symops = reader.symops;
614     int nOps = symops.size();
615     ops = new M4[nOps];
616     for (int i = 0; i < nOps; i++) {
617       ops[i] = SymmetryOperation.getMatrixFromXYZ("!" + symops.get(i));
618     }
619     for (int i = 0; i < atoms.size(); i++) {
620       atoms.get(i).finalizeAtom();
621     }
622     // sym is used only to allow conversion to Cartesian coordinates for the link distance finalization
623     sym = reader.getSymmetry();
624     // we do not have to finalize nodes directly -- finalizeLink will take care of that.
625     for (int i = 0; i < links.size(); i++) {
626       links.get(i).finalizeLink();
627     }
628     for (int i = links.size(); --i >= 0;) {
629       if (!links.get(i).finalized)
630         links.remove(i);
631     }
632 
633     if (reader.doApplySymmetry) {
634       reader.applySymmetryAndSetTrajectory();
635     }
636     if (selectedNet != null)
637       selectNet();
638     return true;
639   }
640 
selectNet()641   private void selectNet() {
642     TNet net = getNetFor(null, selectedNet, false);
643     if (net == null) {
644       net = getNetFor(selectedNet, null, false);
645     }
646     if (net == null)
647       return;
648     BS bsAtoms = reader.asc.bsAtoms;
649     if (bsAtoms == null)
650       bsAtoms = reader.asc.bsAtoms = BSUtil.newBitSet2(0, reader.asc.ac);
651     Atom[] atoms = reader.asc.atoms;
652     for (int i = reader.asc.ac; --i >= 0;) {
653       Atom a = atoms[i];
654       if (!(a instanceof TPoint) || ((TPoint) a).getNet() != net) {
655         bsAtoms.clear(i);
656       }
657     }
658   }
659 
660   /**
661    * Symmetry has been applied. Identify all of the connected atoms and process
662    * the group associations
663    *
664    */
665   @Override
finalizeSymmetry(boolean haveSymmetry)666   public void finalizeSymmetry(boolean haveSymmetry) throws Exception {
667     if (reader == null || !haveSymmetry || links.size() == 0)
668       return;
669 
670     BS bsConnected = new BS(); // atoms that are linked
671     BS bsAtoms = new BS(); // atoms that are associated or connected;
672     int nLinks = processAssociations(bsConnected, bsAtoms);
673     // create the excluded atoms set -- atoms of bsAtoms that are linked
674     BS bsExclude = shiftBits(bsAtoms, bsConnected);
675     // If we have a network, remove all unconnected atoms.
676     if (bsConnected.cardinality() > 0) {
677       reader.asc.bsAtoms = bsAtoms;
678       reader.asc.atomSetInfo.put("bsExcludeBonding", bsExclude);
679     }
680     reader.appendLoadNote("TopoCifParser created " + bsConnected.cardinality()
681         + " nodes and " + nLinks + " links");
682 
683     // add auxiliaryInfo.models[i].topology
684     Lst<Map<String, Object>> info = new Lst<Map<String, Object>>();
685     for (int i = 0, n = links.size(); i < n; i++) {
686       info.addLast(links.get(i).getLinkInfo());
687     }
688     reader.asc.setCurrentModelInfo("topology", info);
689     String script = ""
690         + "if (autobond) {delete !connected && !(atomName LIKE '*_Link*' or atomName LIKE '*_Node*')}; "
691         + "display displayed or " + nets.get(0).label + "__*";
692     reader.addJmolScript(script);
693     for (int i = 0; i < nets.size(); i++) {
694       nets.get(i).finalizeNet();
695     }
696   }
697 
698   /**
699    * Shift bits to the left to account for missing atoms in the final atom list.
700    *
701    * @param bsAtoms
702    * @param bs
703    * @return shifted bitset
704    */
shiftBits(BS bsAtoms, BS bs)705   static BS shiftBits(BS bsAtoms, BS bs) {
706     BS bsNew = new BS();
707     for (int pt = 0, i = bsAtoms.nextSetBit(0); i >= 0; i = bsAtoms
708         .nextSetBit(i + 1)) {
709       while (bsAtoms.get(i)) {
710         bsNew.setBitTo(pt++, bs.get(i++));
711       }
712     }
713     return bsNew;
714   }
715 
716   /**
717    * Find and process all "bonds" associated with all links and nodes. This
718    * method runs AFTER generation of all the symmetry-related atoms.
719    *
720    * BOND_LINK + index indicates linked nodes
721    *
722    * BOND_GROUP + index indicates associated nodes
723    *
724    *
725    * @param bsConnected
726    *        prevent Jmol from adding bonds to this atom
727    * @param bsAtoms
728    *        allow Jmol to add bonds to these atoms, inclusively
729    *
730    * @return number of bonds created
731    */
processAssociations(BS bsConnected, BS bsAtoms)732   private int processAssociations(BS bsConnected, BS bsAtoms) {
733 
734     int nlinks = 0;
735     BS bsAtoms0 = reader.asc.bsAtoms;
736     Atom[] atoms = reader.asc.atoms;
737 
738     // set associations for links and nodes
739     for (int i = reader.asc.ac; --i >= ac0;) {
740       Atom a = atoms[i];
741       if (bsAtoms0 != null && !bsAtoms0.get(i))
742         continue;
743       int idx = a.sequenceNumber;
744       if (idx == Integer.MIN_VALUE || idx == 0)
745         continue;
746       if (idx > 0) {
747         TNode node = getAssociatedNodeByIdx(idx - 1);
748         if (node.bsAtoms == null)
749           node.bsAtoms = new BS();
750         node.bsAtoms.set(i0 + a.index);
751       } else {
752         TLink link = getAssoiatedLinkByIdx(-idx - 1);
753         if (link != null) {
754           if (link.bsAtoms == null)
755             link.bsAtoms = new BS();
756           link.bsAtoms.set(i0 + a.index);
757         }
758       }
759       bsAtoms.set(a.index);
760     }
761 
762     boolean checkDistance = reader.doPackUnitCell;
763     float distance;
764     // finish up with bonds
765     Bond[] bonds = reader.asc.bonds;
766     for (int i = reader.asc.bondCount; --i >= bc0;) {
767       Bond b = bonds[i];
768       if (b.order >= TOPOL_GROUP) {
769         // associated atoms - don't show this bond
770         bonds[i] = null;
771       } else if (b.order >= TOPOL_LINK) {
772         // adjust link bond order, and add this bond to the link's bsBonds bitset
773         if (bsAtoms0 != null
774             && (!bsAtoms0.get(b.atomIndex1) || !bsAtoms0.get(b.atomIndex2))) {
775           bonds[i] = null;
776           continue;
777         }
778         b.order -= TOPOL_LINK;
779         TLink link = getAssoiatedLinkByIdx(b.order >> LINK_TYPE_BITS);
780 
781         if (checkDistance
782             && Math.abs((distance = calculateDistance(atoms[b.atomIndex1],
783                 atoms[b.atomIndex2])) - link.distance) >= ERROR_TOLERANCE) {
784           System.err.println("Distance error! removed! distance=" + distance
785               + " for " + link + link.linkNodes[0] + link.linkNodes[1]);
786           bonds[i] = null;
787           continue;
788         }
789         if (link.bsBonds == null)
790           link.bsBonds = new BS();
791         link.bsBonds.set(b0 + i);
792         switch (b.order & 0xF) {
793         default:
794           b.order = Edge.BOND_COVALENT_SINGLE;
795           break;
796         case LINK_TYPE_DOUBLE:
797           b.order = Edge.BOND_COVALENT_DOUBLE;
798           break;
799         case LINK_TYPE_TRIPLE:
800           b.order = Edge.BOND_COVALENT_TRIPLE;
801           break;
802         case LINK_TYPE_QUADRUPLE:
803           b.order = Edge.BOND_COVALENT_QUADRUPLE;
804           break;
805         case LINK_TYPE_QUINTUPLE:
806           b.order = Edge.BOND_COVALENT_QUINTUPLE;
807           break;
808         case LINK_TYPE_SEXTUPLE:
809           b.order = Edge.BOND_COVALENT_sextuple;
810           break;
811         case LINK_TYPE_POLY:
812           b.order = Edge.BOND_COVALENT_SINGLE;
813           break;
814         case LINK_TYPE_DELO:
815         case LINK_TYPE_PI:
816           b.order = Edge.BOND_AROMATIC;
817           break;
818         case LINK_TYPE_HBOND:
819           b.order = Edge.BOND_H_REGULAR;
820           break;
821         case LINK_TYPE_VDW:
822           b.order = Edge.BOND_PARTIAL01;
823           break;
824         }
825         bsConnected.set(b.atomIndex1);
826         bsConnected.set(b.atomIndex2);
827         nlinks++;
828       }
829     }
830 
831     bsAtoms.or(bsConnected);
832     if (bsAtoms0 != null)
833       bsAtoms.and(bsAtoms0);
834 
835     for (int i = nodes.size(); --i >= 0;) {
836       TNode node = nodes.get(i);
837       if (node.bsAtoms != null) {
838         node.bsAtoms = shiftBits(bsAtoms, node.bsAtoms);
839       }
840     }
841 
842     for (int i = links.size(); --i >= 0;) {
843       TLink link = links.get(i);
844       if (link.bsAtoms != null) {
845         link.bsAtoms = shiftBits(bsAtoms, link.bsAtoms);
846       }
847     }
848 
849     return nlinks;
850   }
851 
isEqualD(T3 p1, T3 p2, double d)852   static boolean isEqualD(T3 p1, T3 p2, double d) {
853     return (Double.isNaN(d) || Math.abs(p1.distance(p2) - d) < ERROR_TOLERANCE);
854   }
855 
856   /**
857    * Read the data value.
858    *
859    * @param key
860    * @return the value or null if does not exist or is '.' or '?'
861    */
getDataValue(byte key)862   private String getDataValue(byte key) {
863     String f = reader.getField(key);
864     return ("\0".equals(f) ? null : f);
865   }
866 
getInt(String f)867   private int getInt(String f) {
868     return (f == null ? Integer.MIN_VALUE : reader.parseIntStr(f));
869   }
870 
getFloat(String f)871   private float getFloat(String f) {
872     return (f == null ? Float.NaN : reader.parseFloatStr(f));
873   }
874 
875   private interface TPoint {
876 
getNet()877     TNet getNet();
878   }
879 
880   private class TNet {
881     @SuppressWarnings("unused")
882     String line;
883     String id;
884     int nLinks, nNodes;
885     String label;
886     String specialDetails;
887 
888     @SuppressWarnings("unused")
889     int idx;
890     boolean hasAtoms;
891 
TNet(int index, String id, String label, String specialDetails)892     TNet(int index, String id, String label, String specialDetails) {
893       idx = index;
894       this.id = id;
895       this.label = label;
896       this.specialDetails = specialDetails;
897     }
898 
finalizeNet()899     void finalizeNet() {
900       if (id == null)
901         id = "" + (idx + 1);
902       if (selectedNet != null && !label.equalsIgnoreCase(selectedNet)
903           && !id.equalsIgnoreCase(selectedNet))
904         return;
905       String netKey = "," + id + ",";
906       if (netNotes.indexOf(netKey) < 0) {
907         reader
908             .appendLoadNote("Net " + label
909                 + (specialDetails == null ? "" : " '" + specialDetails + "'")
910                 + " created from " + nLinks + " links and " + nNodes
911                 + " nodes.\n" + "Use DISPLAY "
912                 + (hasAtoms ? label
913                     + "__* to display it without associated atoms\nUse DISPLAY "
914                     + label + "_* to display it with its associated atoms"
915                     : label + "* to display it" + ""));
916       }
917     }
918   }
919 
920   private class TAtom extends Atom implements TPoint {
921 
922     // from CIF data:
923     @SuppressWarnings("unused")
924     String id;
925     String atomLabel;
926     String nodeID;
927     String linkID;
928     int symop = 0;
929     private P3 trans = new P3();
930     String line;
931 
932     // derived
933     private boolean isFinalized;
934     @SuppressWarnings("unused")
935     int idx;
936     TNet net;
937 
TAtom()938     TAtom() {
939       super();
940       @SuppressWarnings("unused")
941       int i = 0;// old transpiler hack
942     }
943 
getTClone()944     TAtom getTClone() {
945       try {
946         TAtom ta = (TAtom) clone();
947         ta.idx = atomCount++;
948         return ta;
949       } catch (CloneNotSupportedException e) {
950         return null;
951       }
952     }
953 
954     @Override
getNet()955     public TNet getNet() {
956       return net;
957     }
958 
setAtom(int[] a, String line)959     boolean setAtom(int[] a, String line) {
960       this.line = line;
961       if (Float.isNaN(x) != Float.isNaN(y) || Float.isNaN(y) != Float.isNaN(z))
962         return false;
963       idx = atomCount++;
964       if (Float.isNaN(x)) {
965         trans = P3.new3(a[0], a[1], a[2]);
966       } else {
967         symop = 0;
968       }
969       atomName = atomLabel;
970       return true;
971     }
972 
finalizeAtom()973     void finalizeAtom() throws Exception {
974       if (isFinalized)
975         return;
976       isFinalized = true;
977       Atom a = getAtomFromName(atomLabel);
978       setElementSymbol(this, elementSymbol);
979       if (a == null && Float.isNaN(x)) {
980         // no associated atom
981         throw new Exception("_topol_atom: no atom " + atomLabel
982             + " line=" + line);
983       }
984 
985       // check for addition to a TNode
986       TNode node = null;
987       if (nodeID != null) {
988         node = findNode(nodeID, -1, null);
989       }
990 
991       // check for addition to a TLink
992       TLink link = null;
993       if (linkID != null) {
994         link = getLinkById(linkID);
995       }
996 
997       if (node == null && link == null) {
998         System.out.println("TAtom " + this + " ignored");
999         return;
1000       }
1001 
1002       // transfer fields and set the symmetry op [tx ty tz]
1003       if (a != null && Float.isNaN(x)) {
1004         setTAtom(a, this);
1005         applySymmetry(this, ops, symop, trans);
1006       }
1007 
1008       // add this atom to the AtomSetCollection
1009       atomName = atomLabel;
1010 //      System.out.println("TAtom adding " + this);
1011 
1012       if (node != null) {
1013         node.addAtom(this);
1014       }
1015       TAtom ta = this;
1016       if (link != null)
1017         ta = link.addAtom(this);
1018       reader.addCifAtom(this, atomName, null, null);
1019       if (ta != this)
1020         reader.addCifAtom(ta, atomName, null, null);
1021     }
1022 
getLinkById(String linkID)1023     private TLink getLinkById(String linkID) {
1024       for (int i = links.size(); --i >= 0;) {
1025         TLink l = links.get(i);
1026         if (l.id.equalsIgnoreCase(linkID))
1027           return l;
1028       }
1029       return null;
1030     }
1031 
1032     @Override
toString()1033     public String toString() {
1034       return line + " " + super.toString();
1035     }
1036 
1037   }
1038 
getMF(Lst<TAtom> tatoms)1039   static String getMF(Lst<TAtom> tatoms) {
1040     int n = tatoms.size();
1041     if (n < 2)
1042       return (n == 0 ? "" : tatoms.get(0).elementSymbol);
1043     int[] atNos = new int[n];
1044     for (int i = 0; i < n; i++) {
1045       atNos[i] = JmolAdapter
1046           .getElementNumber(tatoms.get(i).getElementSymbol());
1047     }
1048     JmolMolecule m = new JmolMolecule();
1049     m.atNos = atNos;
1050     return m.getMolecularFormula(false, null, false);
1051   }
1052 
setTAtom(Atom a, Atom b)1053   static void setTAtom(Atom a, Atom b) {
1054     b.setT(a);
1055     b.formalCharge = a.formalCharge;
1056     b.bondRadius = a.bondRadius;
1057   }
1058 
1059   /**
1060    *
1061    * @param a
1062    *        TNode or TAtom
1063    * @param sym
1064    */
setElementSymbol(Atom a, String sym)1065   static void setElementSymbol(Atom a, String sym) {
1066     String name = a.atomName;
1067     if (sym == null) {
1068       a.atomName = (a.atomName == null ? "X"
1069           : a.atomName.substring(a.atomName.indexOf('_') + 1));
1070     } else {
1071       a.atomName = sym;
1072     }
1073     a.getElementSymbol();
1074     a.atomName = name;
1075   }
1076 
1077   /**
1078    * Apply the symmetry and translation
1079    *
1080    * @param a
1081    *        TNode or TAtom
1082    * @param ops
1083    * @param op
1084    * @param t
1085    */
applySymmetry(Atom a, M4[] ops, int op, T3 t)1086   static void applySymmetry(Atom a, M4[] ops, int op, T3 t) {
1087     if (op >= 0) {
1088       if (op >= 1 || t.x != 0 || t.y != 0 || t.z != 0) {
1089         if (op >= 1)
1090           ops[op].rotTrans(a);
1091         a.add(t);
1092       }
1093     }
1094   }
1095 
1096   private final static P3 ZERO = new P3();
1097   private class TNode extends Atom implements TPoint {
1098 
1099     public String id;
1100     public String atomLabel;
1101     String netID;
1102     String label;
1103     int symop = 0;
1104     P3 trans = new P3();
1105 
1106     Lst<TAtom> tatoms;
1107     BS bsAtoms = null;
1108 
1109     int linkSymop = 0;
1110     P3 linkTrans = new P3();
1111     TNet net;
1112     private boolean isFinalized;
1113     int idx;
1114     private Atom atom; // legacy
1115     private String line;
1116     private String mf;
1117 
TNode()1118     TNode() {
1119       super();
1120       @SuppressWarnings("unused")
1121       int i = 0;// old transpiler needs this?
1122     }
1123 
1124     /**
1125      * Constructor from TLink
1126      *
1127      * @param idx
1128      * @param atom
1129      * @param net
1130      * @param op
1131      * @param trans
1132      */
TNode(int idx, Atom atom, TNet net, int op, P3 trans)1133     TNode(int idx, Atom atom, TNet net, int op, P3 trans) {
1134       super();
1135       this.idx = idx;
1136       this.atom = atom;
1137       this.net = net;
1138       this.linkSymop = op;
1139       this.linkTrans = trans;
1140       this.label = this.atomName = this.atomLabel = atom.atomName;
1141       this.elementSymbol = atom.elementSymbol;
1142 //      this.formula = atom.getElementSymbol();
1143       setTAtom(atom, this);
1144     }
1145 
getMolecularFormula()1146     public String getMolecularFormula() {
1147       return (mf == null ? (mf = getMF(tatoms)) : mf);
1148     }
1149 
1150 
1151     @Override
getNet()1152     public TNet getNet() {
1153       return net;
1154     }
1155 
setNode(int[] a, String line)1156     boolean setNode(int[] a, String line) {
1157       this.line = line;
1158       if (tatoms == null) {
1159         if (Float.isNaN(x) != Float.isNaN(y)
1160             || Float.isNaN(y) != Float.isNaN(z))
1161           return false;
1162         idx = atomCount++;
1163         if (Float.isNaN(x)) {
1164           trans = P3.new3(a[0], a[1], a[2]);
1165         } else {
1166           symop = 0;
1167         }
1168 //        if (formula != null && formula.indexOf(" ") < 0) {
1169 //          atomName = formula;
1170 //          getElementSymbol();
1171 //          if (!formula.equals(elementSymbol))
1172 //            elementSymbol = "Z";
1173 //          atomName = null;
1174 //        }
1175       }
1176       return true;
1177     }
1178 
addAtom(TAtom atom)1179     void addAtom(TAtom atom) {
1180       if (tatoms == null)
1181         tatoms = new Lst<TAtom>();
1182       atom.atomName = "Node_" + atom.nodeID + "_" + atom.atomLabel;
1183       tatoms.addLast(atom);
1184     }
1185 
finalizeNode(M4[] ops)1186     void finalizeNode(M4[] ops) throws Exception {
1187       if (isFinalized)
1188         return;
1189       isFinalized = true;
1190       if (net == null)
1191         net = getNetFor(netID, null, true);
1192       boolean haveXYZ = !Float.isNaN(x);
1193       Atom a;
1194       if (tatoms == null) {
1195         a = null;
1196 //        a = (atom == null ? getAtomFromName(atomLabel) : atom);
1197         if (!haveXYZ) {
1198           // no assigned atom_site
1199           // no associated atom
1200           // no defined xyz
1201           throw new Exception("_topol_node no atom " + atomLabel
1202               + " line=" + line);
1203         }
1204 //        setElementSymbol(this, a.elementSymbol);
1205       } else {
1206         if (Float.isNaN(x))
1207           setCentroid();
1208         if (tatoms.size() == 1) {
1209           TAtom ta = tatoms.get(0);
1210           elementSymbol = ta.elementSymbol;
1211           atomLabel = ta.atomLabel;
1212           formalCharge = ta.formalCharge;
1213           tatoms = null;
1214         } else {
1215           net.hasAtoms = true;
1216           elementSymbol = "Xx";
1217           for (int i = tatoms.size(); --i >= 0;) {
1218             TAtom ta = tatoms.get(i);
1219             ta.sequenceNumber = idx + 1;
1220             if (ta.atomName == null || !ta.atomName.startsWith(net.label + "_"))
1221               ta.atomName = net.label + "_" + ta.atomName;
1222             ta.net = net;
1223           }
1224         }
1225         a = this;
1226       }
1227       if ((a != null && a == atom) || !haveXYZ) {
1228         if (a != this) {
1229           setTAtom(a, this);
1230         }
1231         applySymmetry(this, ops, symop, trans);
1232       }
1233       atomName = net.label.replace(' ', '_') + "__";
1234       if (label != null && label.startsWith(atomName)) {
1235         atomName = "";
1236       }
1237       atomName += (label != null ? label
1238           : atomLabel != null ? atomLabel : "Node_" + id);
1239       addNode();
1240     }
1241 
addNode()1242     private void addNode() {
1243       reader.addCifAtom(this, atomName, null, null);
1244       net.nNodes++;
1245       if (tatoms != null && tatoms.size() > 1)
1246         reader.appendLoadNote("_topos_node " + id + " " + atomName + " has formula " + getMolecularFormula());
1247     }
1248 
setCentroid()1249     private void setCentroid() {
1250       x = y = z = 0;
1251       int n = tatoms.size();
1252       for (int i = n; --i >= 0;)
1253         add(tatoms.get(i));
1254       x /= n;
1255       y /= n;
1256       z /= n;
1257     }
1258 
info()1259     public String info() {
1260       return "[node idx=" + idx + " id=" + id + " " + label + "/" + atomName + " "
1261           + super.toString() + "]";
1262     }
1263 
1264     @Override
toString()1265     public String toString() {
1266       return info();
1267     }
1268 
copy()1269     public TNode copy() {
1270       TNode node = (TNode) clone();
1271       node.idx = atomCount++;
1272       if (node.isFinalized)
1273         node.addNode();
1274       if (tatoms != null) {
1275         node.tatoms = new Lst<TAtom>();
1276         for (int i = 0, n = tatoms.size(); i < n; i++) {
1277           TAtom ta = tatoms.get(i).getTClone();
1278           node.tatoms.addLast(ta);
1279           reader.addCifAtom(ta, ta.atomName, null, null);
1280         }
1281       }
1282       return node;
1283     }
1284 
1285     @Override
clone()1286     public Object clone() {
1287       try {
1288         return super.clone();
1289       } catch (CloneNotSupportedException e) {
1290         return null;
1291       }
1292     }
1293 
1294   }
1295 
1296   /**
1297    * A class to hold the TOPOL_LINK data item information and transform it as
1298    * needed. A key field is the primitives array of TopoPrimitives. These
1299    * structures allow us to create a set of "primitive" operation results that
1300    * operate specifically on the links themselves. Rather than showing those
1301    * links (as with the Jmol script commented at the end of this class), we
1302    * choose to first create the standard Jmol atom set using, for example, load
1303    * hcb.cif PACKED or load xxx.cif {1 1 1} or load xxx.cif {444 666 1}, etc.
1304    * Then we match those atoms with link edges by unitizing the atom back to its
1305    * unit cell 555 site and then comparing with the primitive associated with a
1306    * given operator.
1307    *
1308    */
1309   private class TLink extends Bond {
1310 
1311     String id;
1312     String[] nodeIds = new String[2];
1313     String[] nodeLabels = new String[2];
1314     int[] symops = new int[2];
1315     P3[] translations = new P3[2];
1316 
1317     String netID;
1318     String netLabel;
1319     String type = "";
1320     int multiplicity;
1321     int topoOrder;
1322     float voronoiAngle;
1323     float cartesianDistance;
1324 
1325     // derived:
1326 
1327     int idx;
1328     TNet net;
1329     TNode[] linkNodes = new TNode[2];
1330 
1331     int typeBondOrder;
1332 
1333     Lst<TAtom> tatoms;
1334     BS bsAtoms;
1335     BS bsBonds;
1336     private String line;
1337     boolean finalized;
1338     private String mf;
1339 
TLink()1340     public TLink() {
1341       super();
1342       @SuppressWarnings("unused")
1343       int i = 0;
1344     }
1345 
setLink(int[] t1, int[] t2, String line)1346     boolean setLink(int[] t1, int[] t2, String line) {
1347       this.line = line;
1348       idx = linkCount++;
1349       if (nodeIds[1] == null)
1350         nodeIds[1] = nodeIds[0];
1351       typeBondOrder = getBondType(type, topoOrder);
1352       // only a relative lattice change is necessary here.
1353       translations[0] = P3.new3(t1[0], t1[1], t1[2]);
1354       translations[1] = P3.new3(t2[0], t2[1], t2[2]);
1355       System.out.println("TopoCifParser.setLink " + this);
1356       return true;
1357     }
1358 
addAtom(TAtom atom)1359     TAtom addAtom(TAtom atom) {
1360       if (tatoms == null)
1361         tatoms = new Lst<TAtom>();
1362       if (atom.nodeID != null) {
1363         atom = atom.getTClone();
1364         atom.nodeID = null;
1365       }
1366       atom.atomName = "Link_" + atom.linkID + "_" + atom.atomLabel;
1367       tatoms.addLast(atom);
1368       return atom;
1369     }
1370 
1371     /**
1372      * Take all actions prior to applying symmetry. Specifically, create any
1373      * nodes and atoms
1374      *
1375      * @throws Exception
1376      */
finalizeLink()1377     void finalizeLink() throws Exception {
1378       netID = (nodeIds[0] == null ? null : findNode(nodeIds[0], -1, null).netID);
1379       if (netID == null && netLabel == null) {
1380         if (nets.size() > 0)
1381           net = nets.get(0);
1382         else
1383           net = getNetFor(null, null, true);
1384       } else {
1385         net = getNetFor(netID, netLabel, true);
1386       }
1387       netLabel = net.label;
1388       net.nLinks++;
1389       if (selectedNet != null) {
1390         if (!selectedNet.equalsIgnoreCase(net.label)
1391             && !selectedNet.equalsIgnoreCase(net.id)) {
1392           return;
1393         }
1394       }
1395       finalizeLinkNode(0);
1396       finalizeLinkNode(1);
1397 
1398       // encode the associated atom sequence number with this link's id.
1399 
1400       if (tatoms != null) {
1401         int n = tatoms.size();
1402         net.hasAtoms = true;
1403         for (int i = n; --i >= 0;) {
1404           TAtom a = tatoms.get(i);
1405           a.sequenceNumber = -idx - 1;
1406           a.atomName = netLabel + "_" + a.atomName;
1407           a.net = net;
1408           //          a.assocDist = new double[] { calculateDistance(linkNodes[0], a), calculateDistance(linkNodes[1], a) };
1409         }
1410         if (n >= 0) {
1411           mf = getMF(tatoms);
1412           reader.appendLoadNote("_topos_link " + id + " for net " + netLabel + " has formula " + mf);
1413         }
1414       }
1415 
1416       // set the Bond fields, encoding the order field with "link", id, and typeBondOrder
1417 
1418       order = TOPOL_LINK + (idx << LINK_TYPE_BITS) + typeBondOrder;
1419       distance = calculateDistance(linkNodes[0], linkNodes[1]);
1420       if (cartesianDistance != 0
1421           && Math.abs(distance - cartesianDistance) >= ERROR_TOLERANCE)
1422         System.err
1423             .println("Distance error! distance=" + distance + " for " + line);
1424       System.out.println(
1425           "link d=" + distance + " " + this + linkNodes[0] + linkNodes[1]);
1426 
1427       reader.asc.addBond(this);
1428       finalized = true;
1429     }
1430 
getMolecularFormula()1431     public String getMolecularFormula() {
1432       return (mf == null ? (mf = getMF(tatoms)) : mf);
1433     }
1434 
1435     /**
1436      *
1437      * @param index
1438      *        0 or 1
1439      * @throws Exception
1440      */
finalizeLinkNode(int index)1441     private void finalizeLinkNode(int index) throws Exception {
1442 
1443       String id = nodeIds[index];
1444       String atomLabel = nodeLabels[index];
1445       int op = symops[index];
1446       P3 trans = translations[index];
1447 
1448       // first check is for a node based on id
1449       TNode node = getNodeWithSym(id, atomLabel, op, trans);
1450       TNode node0 = node;
1451       if (node == null && id != null) {
1452         node = getNodeWithSym(id, null, -1, null);
1453       }
1454       // second check is for an atom_site atom with this label
1455       Atom atom = (node == null && atomLabel != null ? getAtomFromName(atomLabel) : null);
1456       // we now either have a node or an atom_site atom or we have a problem
1457       if (atom != null) {
1458         node = new TNode(atomCount++, atom, net, op, trans);
1459       } else if (node != null) {
1460         if (node0 == null)
1461           node = node.copy();
1462         node.linkSymop = op;
1463         node.linkTrans = trans;
1464         nodeLabels[index] = node.atomName;
1465       } else {
1466         throw new Exception("_topol_link: no atom or node "
1467             + atomLabel + " line=" + line);
1468       }
1469       nodes.addLast(node);
1470       linkNodes[index] = node;
1471       // check for the same node (but different symmetry, of course)
1472       if (index == 1 && node == linkNodes[0]) {
1473         linkNodes[1] = node.copy();
1474       }
1475       node.finalizeNode(ops);
1476       if (node0 == null)
1477         applySymmetry(node, ops, op, trans);
1478       if (index == 0) {
1479         atomIndex1 = node.index;
1480       } else {
1481         atomIndex2 = node.index;
1482       }
1483     }
1484 
1485 //    private void setNet(TNode node) {
1486 //      if (net != null)
1487 //        return;
1488 //      net = (node == null ? getNetFor(netID, netLabel, true) : node.net);
1489 //    }
1490 
1491     /**
1492      * Find a node that already matches this id and symmetry
1493      *
1494      * @param nodeID
1495      * @param nodeLabel
1496      * @param op
1497      *        a symmetry operation [9...N-1] or -1 to ignore op and trans
1498      * @param trans
1499      *        the translation, ignored if op < 0
1500      * @return found node or null
1501      */
getNodeWithSym(String nodeID, String nodeLabel, int op, P3 trans)1502     private TNode getNodeWithSym(String nodeID, String nodeLabel, int op,
1503                                  P3 trans) {
1504       if (nodeID != null)
1505         return findNode(nodeID, op, trans);
1506       for (int i = nodes.size(); --i >= 0;) {
1507         TNode n = nodes.get(i);
1508         if (n.label.equals(nodeLabel)
1509             && (op == -1 && n.linkSymop == 0 && n.linkTrans.equals(ZERO)|| op == n.linkSymop && trans.equals(n.linkTrans)))
1510           return n;
1511       }
1512       return null;
1513     }
1514 
getLinkInfo()1515     Map<String, Object> getLinkInfo() {
1516       Hashtable<String, Object> info = new Hashtable<String, Object>();
1517       info.put("index", Integer.valueOf(idx + 1));
1518       if (id != null)
1519         info.put("id", id);
1520       info.put("netID", net.id);
1521       info.put("netLabel", net.label);
1522       if (nodeLabels[0] != null)
1523         info.put("nodeLabel1", nodeLabels[0]);
1524       if (nodeLabels[1] != null)
1525         info.put("nodeLabel2", nodeLabels[1]);
1526       if (nodeIds[0] != null)
1527         info.put("nodeId1", nodeIds[0]);
1528       if (nodeIds[1] != null)
1529         info.put("nodeId2", nodeIds[1]);
1530       info.put("distance", Float.valueOf(cartesianDistance));
1531       if (!Float.isNaN(distance))
1532         info.put("distance", Float.valueOf(distance));
1533       info.put("symops1", Integer.valueOf(symops[0] + 1));
1534       info.put("symops2", Integer.valueOf(symops[1] + 1));
1535       info.put("translation1", translations[0]);
1536       info.put("translation2", translations[1]);
1537       info.put("multiplicity", Integer.valueOf(multiplicity));
1538       if (type != null)
1539         info.put("type", type);
1540       info.put("voronoiSolidAngle", Float.valueOf(voronoiAngle));
1541       // derived
1542       info.put("atomIndex1", Integer.valueOf(i0 + linkNodes[0].index));
1543       info.put("atomIndex2", Integer.valueOf(i0 + linkNodes[1].index));
1544       if (bsAtoms != null && bsAtoms.cardinality() > 0)
1545         info.put("representedAtoms", bsAtoms);
1546       info.put("topoOrder", Integer.valueOf(topoOrder));
1547       info.put("order", Integer.valueOf(typeBondOrder));
1548       return info;
1549     }
1550 
info()1551     String info() {
1552       return "[link " + line + " : " + distance + "]";
1553     }
1554 
1555     @Override
toString()1556     public String toString() {
1557       return info();
1558     }
1559 
1560   }
1561 
1562   /**
1563    * Find or create a net with this netID, giving it a default name "Net"+id
1564    *
1565    * @param id
1566    * @return net, never null
1567    */
getNetByID(String id)1568   public TNet getNetByID(String id) {
1569     for (int i = nets.size(); --i >= 0;) {
1570       TNet n = nets.get(i);
1571       if (n.id.equalsIgnoreCase(id))
1572         return n;
1573     }
1574     TNet n = new TNet(netCount++, id, "Net" + id, null);
1575     nets.addLast(n);
1576     return n;
1577   }
1578 
getAtomFromName(String atomLabel)1579   public Atom getAtomFromName(String atomLabel) {
1580     return (atomLabel == null ? null : reader.asc.getAtomFromName(atomLabel));
1581   }
1582 
calculateDistance(P3 p1, P3 p2)1583   float calculateDistance(P3 p1, P3 p2) {
1584     temp1.setT(p1);
1585     temp2.setT(p2);
1586     sym.toCartesian(temp1, true);
1587     sym.toCartesian(temp2, true);
1588     return temp1.distance(temp2);
1589   }
1590 
1591   /**
1592    * Find or create a TNet for this id and label.
1593    *
1594    * @param id
1595    *        or null
1596    * @param label
1597    *        or null
1598    * @param forceNew
1599    *        true to create a new net
1600    * @return a net, or null if not forceNew and not found
1601    */
getNetFor(String id, String label, boolean forceNew)1602   public TNet getNetFor(String id, String label, boolean forceNew) {
1603     TNet net = null;
1604     if (id != null) {
1605       net = getNetByID(id);
1606       if (net != null && label != null && forceNew)
1607         net.label = label;
1608     } else if (label != null) {
1609       for (int i = nets.size(); --i >= 0;) {
1610         TNet n = nets.get(i);
1611         if (n.label.equalsIgnoreCase(label)) {
1612           net = n;
1613           break;
1614         }
1615       }
1616     }
1617     if (net == null) {
1618       if (!forceNew)
1619         return null;
1620       net = getNetByID(id == null ? "1" : id);
1621     }
1622     if (net != null && label != null && forceNew)
1623       net.label = label;
1624     return net;
1625   }
1626 
1627   /**
1628    * Find the node for this TAtom.
1629    *
1630    * @param idx
1631    * @return the node or null
1632    */
getAssociatedNodeByIdx(int idx)1633   TNode getAssociatedNodeByIdx(int idx) {
1634     for (int i = nodes.size(); --i >= 0;) {
1635       TNode n = nodes.get(i);
1636       if (n.idx == idx)
1637         return n;
1638     }
1639     return null;
1640   }
1641 
1642   /**
1643    * Find the link for this TAtom.
1644    *
1645    * @param idx
1646    * @return the link or null
1647    */
getAssoiatedLinkByIdx(int idx)1648   TLink getAssoiatedLinkByIdx(int idx) {
1649     for (int i = links.size(); --i >= 0;) {
1650       TLink l = links.get(i);
1651       if (l.idx == idx)
1652         return l;
1653     }
1654     return null;
1655   }
1656 
1657   /**
1658    * Called from TLink and TAtom to find a node with the given symmetry.
1659    *
1660    * @param nodeID
1661    * @param op match for linkSymop
1662    * @param trans match for linkTrans
1663    * @return the node, or null if no such node was found
1664    */
findNode(String nodeID, int op, P3 trans)1665   public TNode findNode(String nodeID, int op, P3 trans) {
1666     for (int i = nodes.size(); --i >= 0;) {
1667       TNode n = nodes.get(i);
1668       if (n.id.equals(nodeID)
1669           && (op < 0 && n.linkSymop == 0 && n.linkTrans.equals(ZERO) || n.linkSymop == op && n.linkTrans.equals(trans)))
1670         return n;
1671     }
1672     return null;
1673   }
1674 
1675 }
1676