1 /* $RCSfile$ 2 * $Author: hansonr $ 3 * $Date: 2010-05-08 19:20:44 -0500 (Sat, 08 May 2010) $ 4 * $Revision: 13038 $ 5 * 6 * Copyright (C) 2005 The Jmol Development Team 7 * 8 * Contact: jmol-developers@lists.sf.net 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Lesser General Public 12 * License as published by the Free Software Foundation; either 13 * version 2.1 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Lesser General Public License for more details. 19 * 20 * You should have received a copy of the GNU Lesser General Public 21 * License along with this library; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 23 */ 24 25 package org.jmol.smiles; 26 27 import java.util.Hashtable; 28 29 import javajs.util.Lst; 30 import javajs.util.Measure; 31 import javajs.util.P3; 32 import javajs.util.V3; 33 34 import javajs.util.BS; 35 import org.jmol.util.BSUtil; 36 import org.jmol.util.Edge; 37 import org.jmol.util.Logger; 38 import org.jmol.util.Node; 39 import org.jmol.util.SimpleNode; 40 41 public class SmilesAromatic { 42 43 /** 44 * Main entry point. Note that unless bonds are pre-defined as aromatic, Jmol 45 * will first check for a flat ring configuration. This is 3D, after all. 46 * 47 * @param n 48 * @param jmolAtoms 49 * @param bsSelected 50 * @param vR 51 * @param bsAromatic 52 * @param strictness 53 * @param isOpenSMILES 54 * @param justCheckBonding 55 * @param checkExplicit 56 * @param v 57 * @param vOK 58 * @param lstSP2 59 * @param eCounts 60 * @param doTestAromatic 61 */ setAromatic(int n, Node[] jmolAtoms, BS bsSelected, Lst<Object> vR, BS bsAromatic, int strictness, boolean isOpenSMILES, boolean justCheckBonding, boolean checkExplicit, VTemp v, Lst<BS> vOK, Lst<SmilesRing> lstSP2, int[] eCounts, boolean doTestAromatic)62 static void setAromatic(int n, Node[] jmolAtoms, BS bsSelected, 63 Lst<Object> vR, BS bsAromatic, int strictness, 64 boolean isOpenSMILES, boolean justCheckBonding, 65 boolean checkExplicit, VTemp v, 66 Lst<BS> vOK, Lst<SmilesRing> lstSP2, 67 int[] eCounts, boolean doTestAromatic) { 68 69 boolean doCheck = (isOpenSMILES || strictness > 0); 70 71 // "strict" means we want true Hueckel 4n+2 72 // -- relax planarity, as bonding should be more important 73 74 if (!doTestAromatic) { 75 // if the aromaticity has been set by the SMILES string itself, we 76 // just collect the aromatic rings here 77 for (int r = vR.size(); --r >= 0;) { 78 BS bs = BSUtil.copy((BS) vR.get(r)); 79 bs.and(bsAromatic); 80 if (bs.cardinality() == n) 81 vOK.addLast(bs); 82 } 83 return; 84 } 85 for (int r = vR.size(); --r >= 0;) { 86 BS bs = (BS) vR.get(r); 87 boolean isOK = isSp2Ring(n, jmolAtoms, bsSelected, bs, 88 (justCheckBonding ? Float.MAX_VALUE : strictness > 0 ? 0.1f : 0.01f), 89 checkExplicit, 90 strictness == 0); 91 if (!isOK) 92 continue; 93 bsAromatic.or(bs); 94 if (doCheck) { 95 // we will need to check these edges later 96 Lst<Edge> edges = new Lst<Edge>(); 97 for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) { 98 Node a = jmolAtoms[i]; 99 Edge[] aedges = a.getEdges(); 100 int ai = a.getIndex(); 101 for (int j = aedges.length; --j >= 0;) { 102 SimpleNode a2 = aedges[j].getOtherNode(a); 103 int a2i = a2.getIndex(); 104 if (a2i > ai && bs.get(a2i)) 105 edges.addLast(aedges[j]); 106 } 107 } 108 switch (checkHueckelAromatic(n, jmolAtoms, bsAromatic, bs, 109 strictness, eCounts)) { 110 case -1: // absolutely not 111 continue; 112 case 0: // maybe -- needs fused ring check 113 isOK = false; 114 //$FALL-THROUGH$ 115 case 1: 116 if (lstSP2 != null) 117 lstSP2.addLast(new SmilesRing(n, bs, edges, isOK)); 118 if (!isOK) 119 continue; 120 } 121 } 122 vOK.addLast(bs); 123 } 124 } 125 126 ////////////////////////// Aromaticity Explicitly Defined by User //////////////////////// 127 128 /** 129 * Set aromatic atoms based on predefined BOND_AROMATIC definitions. 130 * Allows for totally customized creation of aromatic SMILES. 131 * 132 * @param jmolAtoms 133 * @param bsSelected 134 * @param bsAromatic 135 */ checkAromaticDefined(Node[] jmolAtoms, BS bsSelected, BS bsAromatic)136 static void checkAromaticDefined(Node[] jmolAtoms, BS bsSelected, BS bsAromatic) { 137 for (int i = bsSelected.nextSetBit(0); i >= 0; i = bsSelected 138 .nextSetBit(i + 1)) { 139 Edge[] bonds = jmolAtoms[i].getEdges(); 140 for (int j = 0; j < bonds.length; j++) { 141 switch (bonds[j].order) { 142 case Edge.BOND_AROMATIC: 143 case Edge.BOND_AROMATIC_DOUBLE: 144 case Edge.BOND_AROMATIC_SINGLE: 145 bsAromatic.set(bonds[j].getAtomIndex1()); 146 bsAromatic.set(bonds[j].getAtomIndex2()); 147 } 148 } 149 } 150 } 151 152 153 154 /////////////////////////// 3D SMILES methods /////////////////////// 155 156 /** 157 * 3D-SEARCH aromaticity test. 158 * 159 * A simple and unambiguous test for aromaticity based on 3D geometry and 160 * connectivity only, not Hueckel theory. 161 * 162 * @param n 163 * 164 * @param atoms 165 * a set of atoms with coordinate positions and associated bonds. 166 * @param bs 167 * a bitset of atoms within the set of atoms, defining the ring 168 * @param bsSelected 169 * must not be null 170 * @param cutoff 171 * an arbitrary value to test the standard deviation against. 0.01 is 172 * appropriate here. Use Float.MAX_VALUE to just do bond connectivity 173 * check 174 * @param checkExplicit 175 * check bonds that are explicit only - for XYZ and QM calcs 176 * @param allowSOxide 177 * set TRUE to skip S atoms 178 * @return true if standard deviation of vNorm.dot.vMean is less than cutoff 179 */ 180 isSp2Ring(int n, Node[] atoms, BS bsSelected, BS bs, float cutoff, boolean checkExplicit, boolean allowSOxide)181 private final static boolean isSp2Ring(int n, Node[] atoms, BS bsSelected, 182 BS bs, float cutoff, 183 boolean checkExplicit, 184 boolean allowSOxide) { 185 /// 186 // 187 // Bob Hanson, hansonr@stolaf.edu 188 // 189 // Given a ring of N atoms... 190 // 191 // 1 192 // / \ 193 // 2 6 -- 6a 194 // | | 195 // 5a -- 5 4 196 // \ / 197 // 3 198 // 199 // with arbitrary order and up to N substituents 200 // 201 // 1) Check to see if all ring atoms have no more than 3 connections. 202 // Note: An alternative definition might include "and no substituent 203 // is explicitly double-bonded to its ring atom, as in quinone. 204 // Here we opt to allow the atoms of quinone to be called "aromatic." 205 // 2) Select a cutoff value close to zero. We use 0.01 here. 206 // 3) Generate a set of normals as follows: 207 // a) For each ring atom, construct the normal associated with the plane 208 // formed by that ring atom and its two nearest ring-atom neighbors. 209 // b) For each ring atom with a substituent, construct a normal 210 // associated with the plane formed by its connecting substituent 211 // atom and the two nearest ring-atom neighbors. 212 // c) If this is the first normal, assign vMean to it. 213 // d) If this is not the first normal, check vNorm.dot.vMean. If this 214 // value is less than zero, scale vNorm by -1. 215 // e) Add vNorm to vMean. 216 // 4) Calculate the standard deviation of the dot products of the 217 // individual vNorms with the normalized vMean. 218 // 5) The ring is deemed flat if this standard deviation is less 219 // than the selected cutoff value. 220 // 221 // Efficiencies: 222 // 223 // 1) Precheck bond counts. 224 // 225 // 2) Each time a normal is added to the running mean, test to see if 226 // its dot product with the mean is within 5 standard deviations. 227 // If it is not, return false. Note that it can be shown that for 228 // a set of normals, even if all are aligned except one, with dot product 229 // to the mean x, then the standard deviation will be (1 - x) / sqrt(N). 230 // Given even an 8-membered ring, this still 231 // results in a minimum value of x of about 1-4c (allowing for as many as 232 // 8 substituents), considerably better than our 1-5c. 233 // So 1-5c is a very conservative test. 234 // 235 // 3) One could probably dispense with the actual standard deviation 236 // calculation, as it is VERY unlikely that an actual nonaromatic ring 237 // (other than quinones and other such compounds) 238 // would have any chance of passing the first two tests. 239 // 240 241 if (checkExplicit) { 242 for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) 243 if (atoms[i].getCovalentBondCount() > 3) 244 return false; 245 } else { 246 for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) 247 if (atoms[i].getCovalentBondCountPlusMissingH() > 3) 248 return false; 249 } 250 if (cutoff == Float.MAX_VALUE) 251 return true; 252 253 if (cutoff <= 0) 254 cutoff = 0.01f; 255 256 V3 vNorm = null; 257 V3 vTemp = null; 258 V3 vMean = null; 259 int nPoints = bs.cardinality(); 260 V3[] vNorms = new V3[nPoints * 2]; 261 int nNorms = 0; 262 float maxDev = (1 - cutoff * 5); 263 for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) { 264 Node ringAtom = atoms[i]; 265 Edge[] bonds = ringAtom.getEdges(); 266 // if more than three connections, ring cannot be fully conjugated 267 // identify substituent and two ring atoms 268 int iSub = -1; 269 int r1 = -1; 270 int r2 = -1; 271 for (int k = bonds.length; --k >= 0;) { 272 int iAtom = ringAtom.getBondedAtomIndex(k); 273 if (!bsSelected.get(iAtom)) 274 continue; 275 // OpenSMILES allows tetrahedral S 276 if (!bs.get(iAtom)) { 277 if (ringAtom.getElementNumber() == 16) { 278 if (!allowSOxide) 279 return false; 280 iAtom = -1; 281 } 282 iSub = iAtom; 283 } else if (r1 < 0) { 284 r1 = iAtom; 285 } else { 286 r2 = iAtom; 287 } 288 } 289 290 if (vMean == null) { 291 vMean = new V3(); 292 vNorm = new V3(); 293 vTemp = new V3(); 294 } 295 296 // check the normal for r1 - i - r2 plane 297 // check the normal for r1 - iSub - r2 plane 298 299 for (int k = 0, j = i; k < 2; k++) { 300 Measure.getNormalThroughPoints((P3) atoms[r1], (P3) atoms[j], (P3) atoms[r2], 301 vNorm, vTemp); 302 if (!addNormal(vNorm, vMean, maxDev)) 303 return false; 304 vNorms[nNorms++] = V3.newV(vNorm); 305 if ((j = iSub) < 0) 306 break; 307 } 308 } 309 return checkStandardDeviation(vNorms, vMean, nNorms, cutoff); 310 } 311 312 /** 313 * adds a normal if similarity is within limits 314 * 315 * @param vTemp 316 * @param vMean 317 * @param maxDev 318 * @return true if successful 319 */ addNormal(V3 vTemp, V3 vMean, float maxDev)320 private final static boolean addNormal(V3 vTemp, V3 vMean, float maxDev) { 321 float similarity = vMean.dot(vTemp); 322 if (similarity != 0 && Math.abs(similarity) < maxDev) 323 return false; 324 if (similarity < 0) 325 vTemp.scale(-1); 326 vMean.add(vTemp); 327 vMean.normalize(); 328 return true; 329 } 330 331 /** 332 * calculates a dot-product standard deviation and reports if it is below a 333 * cutoff 334 * 335 * @param vNorms 336 * @param vMean 337 * @param n 338 * @param cutoff 339 * @return true if stddev < cutoff 340 */ checkStandardDeviation(V3[] vNorms, V3 vMean, int n, float cutoff)341 private final static boolean checkStandardDeviation(V3[] vNorms, V3 vMean, 342 int n, float cutoff) { 343 double sum = 0; 344 double sum2 = 0; 345 for (int i = 0; i < n; i++) { 346 float v = vNorms[i].dot(vMean); 347 sum += v; 348 sum2 += ((double) v) * v; 349 } 350 sum = Math.sqrt((sum2 - sum * sum / n) / (n - 1)); 351 return (sum < cutoff); 352 } 353 354 355 356 ////////////////////////////// OpenSMILES Specification for Aromaticity ////////////////////////// 357 358 /** 359 * 360 * Index to inner array is covalent bond count (b) + valence (v) + charge (c, 361 * carbon only) - 4. Special cases are listed here as -1. -2 indicates not 362 * considered aromatic (probably not possible). 363 * 364 * Many thanks to John May for the excellent visual guide that I have 365 * condensed here. 366 * 367 */ 368 final static private int[][] OS_PI_COUNTS = { 369 { -2, 1, 0 }, // 0 B b+v-4 370 { 1, 2, 1, -1 }, // 1 C b+v+c-4 371 { 2, 1, 2, 1, 1 }, // 2 N,P b+v-4 372 { 2, 1 }, // 3 O,Se b+v-4 373 { -2, 1, 2, 1, -2 }, // 4 As b+v-4 374 { 2, 1, 2, 2 } // 5 S b+v-4 375 }; 376 377 /** 378 * For each atom in the ring, look up a unique combination of covalent bond 379 * count, valence, and charge for each atom and use that as a key into the 380 * PI_COUNTS array. c=X is the only special case. Final trimming will be 381 * necesseary if isStrict == true. 382 * 383 * @param nAtoms 384 * this ring's size 385 * @param jmolAtoms 386 * could also be constructed nodes from a SMILES string 387 * @param bsAromatic 388 * at least nominally aromatic atoms 389 * @param bsRing 390 * specific atoms of this ring 391 * @param strictness 392 * 0 (not) 1 (OpenSMILES), 2 (MMFF94) standard organic chemist's 393 * Hueckel interpretation, not allowing c=O 394 * @return -1 if absolutely not possible, 0 if possible but not 4n+2, 1 if 395 * 4n+2 396 * @author Bob Hanson 3/12/2016 397 * @param eCounts 398 * 399 */ checkHueckelAromatic(int nAtoms, Node[] jmolAtoms, BS bsAromatic, BS bsRing, int strictness, int[] eCounts)400 private static int checkHueckelAromatic(int nAtoms, Node[] jmolAtoms, 401 BS bsAromatic, BS bsRing, 402 int strictness, int[] eCounts) { 403 int npi = 0; // total number of pi electrons 404 int n1 = 0; // total number of atoms contributing exactly 1 electron (for strictness==2) 405 for (int i = bsRing.nextSetBit(0); i >= 0 && npi >= 0; i = bsRing 406 .nextSetBit(i + 1)) { 407 Node atom = jmolAtoms[i]; 408 int z = atom.getElementNumber(); 409 int n = atom.getCovalentBondCountPlusMissingH(); 410 n += atom.getValence(); 411 n -= 4; 412 if (z == 6) { 413 int fc = atom.getFormalCharge(); // add in charge for C 414 if (fc != Integer.MIN_VALUE) // SmilesAtom charge not set 415 n += fc; 416 } 417 int pt = (z >= 5 && z <= 8 ? z - 5 // B, C, N, O 418 : z == 15 ? 2 // P -> N 419 : z == 34 ? 3 // Se - > O 420 : z == 33 ? 4 // As special 421 : z == 16 ? 5 // S special 422 : -1); 423 if (pt >= 0) { 424 int[] a = OS_PI_COUNTS[pt]; 425 if (n < 0 || n >= a.length) 426 return -1; 427 switch (n = a[n]) { 428 case -2: 429 // not a connection/valence/charge of interest 430 return -1; 431 case -1: 432 // check for c=X(nonaromatic) 0 433 // against c[c+1](C)c (0) and c=c (1) 434 Edge[] bonds = atom.getEdges(); 435 n = 0; // no double bond; sp2 c cation 436 for (int j = bonds.length; --j >= 0;) { 437 Edge b = bonds[j]; 438 if (b.getCovalentOrder() != 2) 439 continue; 440 // just check that the connected atom is either C or flat-aromatic 441 // if it is, assign 1 pi electron; if it is not, set it to 0 as long 442 // we are not being strict or discard this ring if we are. 443 SimpleNode het = b.getOtherNode(atom); 444 n = (het.getElementNumber() == 6 || bsAromatic.get(het.getIndex()) ? 1 445 : strictness > 0 ? -100 : 0); 446 break; 447 } 448 //$FALL-THROUGH$ 449 default: 450 // ok -- add in the number of pi electrons for this atom 451 if (n < 0) 452 return -1; 453 if (eCounts != null) 454 eCounts[i] = n; 455 npi += n; 456 if (n == 1) 457 n1++; 458 if (Logger.debuggingHigh) 459 Logger.info("atom " + atom + " pi=" + n + " npi=" + npi); 460 // normal continuance 461 continue; 462 } 463 } 464 } 465 // Hueckel: npi =?= 4n + 2 466 // MMFF94: all atoms must contribute 1 for 6- or 7-membered rings (3 double bonds) 467 return ((npi - 2) % 4 == 0 && (strictness < 2 || nAtoms == 5 || n1 == 6) ? 1 : 0); 468 } 469 470 /** 471 * Iteratively trims a set of aromatic atoms that may be initially assigned to 472 * be aromatic but because their double bonds extend to non-aromatic atoms 473 * must be removed. Checks to see that these atoms really have two adjacent 474 * aromatic atoms and are not connected to any nonaromatic atom by a double 475 * bond. 476 * 477 * Could be entered with one of /open/, /open strict/, or /strict/ 478 * 479 * @param jmolAtoms 480 * @param bsAromatic 481 * @param lstAromatic all rings passing the sp2 test and (if strict) the Hueckel strict test 482 * @param lstSP2 all rings passing the sp2 test 483 * @param eCounts 484 * @param isOpenNotStrict 485 * /open/ option 486 * @param isStrict 487 * remove noncyclic double bonds and do not allow bridging aromatic 488 * ring systems (/strict/ option) 489 */ finalizeAromatic(Node[] jmolAtoms, BS bsAromatic, Lst<BS> lstAromatic, Lst<SmilesRing> lstSP2, int[] eCounts, boolean isOpenNotStrict, boolean isStrict)490 static void finalizeAromatic(Node[] jmolAtoms, BS bsAromatic, 491 Lst<BS> lstAromatic, Lst<SmilesRing> lstSP2, 492 int[] eCounts, boolean isOpenNotStrict, 493 boolean isStrict) { 494 495 // strictly speaking, there is no such thing as a bridged aromatic pi system 496 if (isStrict) 497 removeBridgingRings(lstAromatic, lstSP2); 498 499 // we allow for combined 4n+2 even if contributing rings are not (5+7 for azulene) 500 checkFusedRings(lstSP2, eCounts, lstAromatic); 501 502 // regenerate bsAromatic, using only valid rings now 503 bsAromatic.clearAll(); 504 for (int i = lstAromatic.size(); --i >= 0;) 505 bsAromatic.or(lstAromatic.get(i)); 506 507 if (isStrict || isOpenNotStrict) { 508 509 // Check each aromatic atom for 510 // (a) no exocyclic double bonds that are not part of an aromatic ring (biphenylene; strict only) 511 // (b) no exocyclic double bonds to nonaromatic atoms ( strict only) 512 // (c) at least two adjacent aromatic atoms (Hueckel cyclic requirement) 513 514 515 for (int i = bsAromatic.nextSetBit(0); i >= 0; i = bsAromatic 516 .nextSetBit(i + 1)) { 517 Edge[] bonds = jmolAtoms[i].getEdges(); 518 int naro = 0; 519 for (int j = bonds.length; --j >= 0;) { 520 SimpleNode otherAtom = bonds[j].getOtherNode(jmolAtoms[i]); 521 int order = bonds[j].getCovalentOrder(); 522 int ai2 = otherAtom.getIndex(); 523 boolean isJAro = bsAromatic.get(ai2); 524 if (isJAro) { 525 if (order == 2) { 526 // test (a) 527 // make sure these two aromatic atoms are in the same aromatic ring 528 boolean isOK = false; 529 for (int k = lstSP2.size(); --k >= 0;) { 530 SmilesRing r = lstSP2.get(k); 531 if (r.get(i) && r.get(ai2)) { 532 isOK = true; 533 break; 534 } 535 } 536 if (!isOK) { 537 naro = -1; 538 break; 539 } 540 } 541 naro++; 542 } else if (isStrict && otherAtom.getElementNumber() == 6 543 && order == 2) { 544 // test (b) 545 naro = -1; 546 break; 547 } 548 } 549 if (naro < 2) { 550 // test (c) 551 bsAromatic.clear(i); 552 // reiterate 553 i = -1; 554 } 555 } 556 } 557 } 558 559 /** 560 * check for any two rings with more than two common atoms and remove them 561 * from the pool 562 * 563 * @param lstAromatic 564 * @param lstSP2 565 */ removeBridgingRings(Lst<BS> lstAromatic, Lst<SmilesRing> lstSP2)566 private static void removeBridgingRings(Lst<BS> lstAromatic, Lst<SmilesRing> lstSP2) { 567 BS bs = new BS(); 568 BS bsBad = new BS(); 569 BS bsBad2 = new BS(); 570 checkBridges(lstAromatic, bsBad, lstAromatic, bsBad, bs); 571 checkBridges(lstSP2, bsBad2, lstSP2, bsBad2, bs); 572 checkBridges(lstAromatic, bsBad, lstSP2, bsBad2, bs); 573 for (int i = lstAromatic.size(); --i >= 0;) 574 if (bsBad.get(i)) 575 lstAromatic.removeItemAt(i); 576 for (int i = lstSP2.size(); --i >= 0;) 577 if (bsBad2.get(i)) 578 lstSP2.removeItemAt(i); 579 } 580 checkBridges(Lst<?> lst, BS bsBad, Lst<?> lst2, BS bsBad2, BS bs)581 private static void checkBridges(Lst<?> lst, BS bsBad, Lst<?> lst2, 582 BS bsBad2, BS bs) { 583 boolean isSameList = (lst == lst2); 584 for (int i = lst.size(); --i >= 0;) { 585 BS bs1 = (BS) lst.get(i); 586 for (int j0 = (isSameList ? i + 1 : 0), j = lst2.size(); --j >= j0;) { 587 BS bs2 = (BS) lst2.get(j); 588 if (bs2.equals(bs1)) 589 continue; 590 bs.clearAll(); 591 bs.or(bs1); 592 bs.and(bs2); 593 int n = bs.cardinality(); 594 if (n > 2) { 595 bsBad.set(i); 596 bsBad2.set(j); 597 } 598 } 599 } 600 } 601 602 /** 603 * Add fused rings based on the Hueckel 4n+2 rule. Note that this may be 604 * reverted later if in a STRICT setting, because in at this point in some 605 * cases we will have double bonds to exocyclic nonaromatic atoms. 606 * 607 * We are being careful here to only group FUSED rings -- that is rings that 608 * have only one bond in common. 609 * 610 * @param rings 611 * @param eCounts 612 * @param lstAromatic 613 * list to be appended to 614 */ checkFusedRings(Lst<SmilesRing> rings, int[] eCounts, Lst<BS> lstAromatic)615 private static void checkFusedRings(Lst<SmilesRing> rings, int[] eCounts, 616 Lst<BS> lstAromatic) { 617 Hashtable<String, SmilesRingSet> htEdgeMap = new Hashtable<String, SmilesRingSet>(); 618 for (int i = rings.size(); --i >= 0;) { 619 SmilesRing r = rings.get(i); 620 Lst<Edge> edges = r.edges; 621 for (int j = edges.size(); --j >= 0;) { 622 SmilesRingSet set = SmilesRing.getSetByEdge(edges.get(j), htEdgeMap); 623 if (set == null || set == r.set) 624 continue; 625 // TODO what about bridging? 626 if (r.set != null) 627 set.addSet(r.set, htEdgeMap); 628 else 629 set.addRing(r); 630 } 631 (r.set == null ? r.set = new SmilesRingSet() : r.set).addRing(r); 632 r.addEdges(htEdgeMap); 633 } 634 SmilesRingSet set; 635 SmilesRing r; 636 for (int i = rings.size(); --i >= 0;) { 637 if ((r = rings.get(i)).isOK || (set = r.set) == null || set.isEmpty()) 638 continue; 639 if ((set.getElectronCount(eCounts) % 4) == 2) 640 for (int j = set.size(); --j >= 0;) 641 if (!(r = set.get(j)).isOK) 642 lstAromatic.addLast(r); 643 set.clear(); 644 } 645 } 646 647 } 648