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