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