1 /* 2 * Copyright (C) 2012 John May <jwmay@users.sf.net> 3 * 4 * Contact: cdk-devel@lists.sourceforge.net 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU Lesser General Public License as published by the 8 * Free Software Foundation; either version 2.1 of the License, or (at your 9 * option) any later version. All we ask is that proper credit is given for our 10 * work, which includes - but is not limited to - adding the above copyright 11 * notice to the beginning of your source code files, and to any copyright 12 * notice that you may distribute with programs based on this work. 13 * 14 * This program is distributed in the hope that it will be useful, but WITHOUT 15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 17 * for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public License 20 * along with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 */ 23 package org.openscience.cdk.ringsearch; 24 25 import org.openscience.cdk.exception.NoSuchAtomException; 26 import org.openscience.cdk.graph.GraphUtil; 27 import org.openscience.cdk.interfaces.IAtom; 28 import org.openscience.cdk.interfaces.IAtomContainer; 29 import org.openscience.cdk.interfaces.IBond; 30 import org.openscience.cdk.interfaces.IChemObjectBuilder; 31 32 import java.util.ArrayList; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.NoSuchElementException; 36 import java.util.Set; 37 38 /** 39 * Efficiently search for atoms that are members of a ring. A depth first search 40 * (DFS) determines which vertices belong to cycles (rings). As cycles are 41 * discovered they are separated into two sets of cycle systems, fused and 42 * isolated. A ring is in a fused cycle systems if it shares at least one edge 43 * (bond) with another cycle. The isolated cycles consist of cycles which at 44 * most share only one vertex (atom) with another cyclic system. A molecule may 45 * contain more then one isolated and/or fused cycle system (see. Examples). 46 * Additional computations such as C<sub>R</sub> (relevant cycles), Minimum 47 * Cycle Basis (MCB) (aka. Smallest Set of Smallest Rings (SSSR)) or the Set of 48 * All Rings can be completely bypassed for members of the isolated rings. Since 49 * every isolated cycle (ring) does not share any edges (bonds) with any other 50 * elementary cycle it cannot be made by composing any other cycles (rings). 51 * Therefore, all isolated cycles (rings) are relevant and are members of all 52 * minimum cycle bases (SSSRs). <b>Important</b> the cycle sets returned are not 53 * ordered in the path of the cycle. 54 * 55 * <br> <b>Further Explanation</b> The diagram below illustrates the isolated 56 * and fused sets of cyclic atoms. The colored circles indicate the atoms and 57 * bonds that are returned for each molecules. <br><br> <img alt="isolated and 58 * fused cycle systems" src="http://cdk.github.io/cdk/img/isolated-and-fused-cycles-01_zpse0311377.PNG"> 59 * <br> <ol type="a"> <li>Two separate isolated cycles</li> <li>Two 60 * separate fused cycle systems. The bridged systems are fused but separate from 61 * each other</li> <li>Fused rings - a single fused cycle system</li> <li>Spiro 62 * rings - three separate isolated systems, no bonds are shared</li> 63 * <li>Cyclophane - a single fused system, the perimeter rings share bonds with 64 * the smaller rings </li> <li>One isolated system and one fused system</li> 65 * </ol> 66 * 67 * <br> 68 * <b>Example Usage</b> 69 * <blockquote><pre>{@code 70 * // construct the search for a given molecule, if an adjacency list 71 * // representation (int[][]) is available this can be passed to the 72 * // constructor for improved performance 73 * IAtomContainer container = ...; 74 * RingSearch ringSearch = new RingSearch(container); 75 * 76 * // indices of cyclic vertices 77 * int[] cyclic = ringSearch.cyclic(); 78 * 79 * // iterate over fused systems (atom indices) 80 * for(int[] fused : ringSearch.fused()){ 81 * ... 82 * } 83 * 84 * // iterate over isolated rings (atom indices) 85 * for(int[] isolated : ringSearch.isolated()){ 86 * ... 87 * } 88 * 89 * // convenience methods for getting the fragments 90 * IAtomContainer cyclic = ringSearch.ringFragments(); 91 * 92 * for(IAtomContainer fragment : ringSearch.fusedRingFragments()){ 93 * .... 94 * } 95 * for(IAtomContainer fragment : ringSearch.isolatedRingFragments()){ 96 * .... 97 * } 98 * }</pre></blockquote> 99 * 100 * @author John May 101 * @cdk.module core 102 * @cdk.githash 103 * @see <a href="http://en.wikipedia.org/wiki/Cycle_(graph_theory)">Cycle (Graph 104 * Theory) - Wikipedia</a> 105 * @see <a href="http://efficientbits.blogspot.co.uk/2012/12/scaling-up-faster-ring-detection-in-cdk.html">Scaling 106 * Up: Faster Ring Detecting in CDK - Efficient Bits, Blog</a> 107 * @see org.openscience.cdk.graph.SpanningTree 108 * @see SSSRFinder 109 * @see AllRingsFinder 110 * @see CyclicVertexSearch 111 */ 112 public final class RingSearch { 113 114 /* depending on molecule size, delegate the search to one of two sub-classes */ 115 private final CyclicVertexSearch searcher; 116 117 /* input atom container */ 118 private final IAtomContainer container; 119 120 /** 121 * Create a new RingSearch for the specified container. 122 * 123 * @param container non-null input structure 124 * @throws NullPointerException if the container was null 125 * @throws IllegalArgumentException if the container contains a bond which 126 * references an atom which could not be 127 * found 128 */ RingSearch(IAtomContainer container)129 public RingSearch(IAtomContainer container) { 130 this(container, GraphUtil.toAdjList(container)); 131 } 132 133 /** 134 * Create a new RingSearch for the specified container and graph. The 135 * adjacency list allows much faster graph traversal but is not free to 136 * create. If the adjacency list representation of the input container has 137 * already been created you can bypass the creation with this constructor. 138 * 139 * @param container non-null input structure 140 * @param graph non-null adjacency list representation of the container 141 * @throws NullPointerException if the container or graph was null 142 */ RingSearch(IAtomContainer container, int[][] graph)143 public RingSearch(IAtomContainer container, int[][] graph) { 144 this(container, makeSearcher(graph)); 145 } 146 147 /** 148 * Create a new RingSearch for the specified container using the provided 149 * search. 150 * 151 * @param container non-null input structure 152 * @param searcher non-null adjacency list representation of the container 153 * @throws NullPointerException if the container or searcher was null 154 */ RingSearch(IAtomContainer container, CyclicVertexSearch searcher)155 public RingSearch(IAtomContainer container, CyclicVertexSearch searcher) { 156 if (container == null) throw new NullPointerException("container must not be null"); 157 if (searcher == null) throw new NullPointerException("searcher was null"); 158 this.searcher = searcher; 159 this.container = container; 160 } 161 162 /** 163 * Utility method making a new {@link CyclicVertexSearch} during 164 * construction. 165 * 166 * @param graph non-null graph 167 * @return a new cyclic vertex search for the given graph 168 * @throws NullPointerException if the graph was null 169 */ makeSearcher(int[][] graph)170 private static CyclicVertexSearch makeSearcher(int[][] graph) { 171 172 if (graph == null) throw new NullPointerException("graph[][] must not be null"); 173 174 // if the molecule has 64 or less atoms we can use single 64 bit long 175 // values to represent our sets of vertices 176 if (graph.length <= 64) { 177 return new RegularCyclicVertexSearch(graph); 178 } else { 179 return new JumboCyclicVertexSearch(graph); 180 } 181 } 182 183 /** 184 * Access the number of rings found (aka. circuit rank, SSSR size). 185 * 186 * @return number of rings 187 * @see <a href="https://en.wikipedia.org/wiki/Circuit_rank">Circuit Rank</a> 188 */ numRings()189 public int numRings() { 190 return searcher.numCycles(); 191 } 192 193 /** 194 * Determine whether the edge between the vertices <i>u</i> and <i>v</i> is 195 * cyclic. 196 * 197 * @param u an end point of the edge 198 * @param v another end point of the edge 199 * @return whether the edge formed by the given end points is in a cycle 200 */ cyclic(int u, int v)201 public boolean cyclic(int u, int v) { 202 return searcher.cyclic(u, v); 203 } 204 205 /** 206 * Determine whether the provided atom belongs to a ring (is cyclic). 207 * 208 * <blockquote><pre>{@code 209 * IAtomContainer mol = ...; 210 * RingSearch ringSearch = new RingSearch(mol); 211 * 212 * for(IAtom atom : mol.atoms()){ 213 * if(ringSearch.cyclic(atom)){ 214 * ... 215 * } 216 * } 217 * }</pre></blockquote> 218 * 219 * @param atom an atom 220 * @return whether the atom is in a ring 221 * @throws NoSuchAtomException the atom was not found 222 */ cyclic(IAtom atom)223 public boolean cyclic(IAtom atom) { 224 int i = container.indexOf(atom); 225 if (i < 0) throw new NoSuchAtomException("no such atom"); 226 return cyclic(i); 227 } 228 229 /** 230 * Determine whether the bond is cyclic. Note this currently requires a 231 * linear search to look-up the indices of each atoms. 232 * 233 * @param bond a bond of the container 234 * @return whether the vertex at the given index is in a cycle 235 */ cyclic(IBond bond)236 public boolean cyclic(IBond bond) { 237 // XXX: linear search - but okay for now 238 int u = container.indexOf(bond.getBegin()); 239 int v = container.indexOf(bond.getEnd()); 240 if (u < 0 || v < 0) throw new NoSuchElementException("atoms of the bond are not found in the container"); 241 return searcher.cyclic(u, v); 242 } 243 244 /** 245 * Determine whether the vertex at index <i>i</i> is a cyclic vertex. 246 * 247 * <blockquote><pre>{@code 248 * IAtomContainer mol = ...; 249 * RingSearch tester = new RingSearch(mol); 250 * 251 * int n = mol.getAtomCount(); 252 * for(int i = 0; i < n; i++){ 253 * if(tester.cyclic(i)){ 254 * ... 255 * } 256 * } 257 * }</pre></blockquote> 258 * 259 * @param i atom index 260 * @return whether the vertex at the given index is in a cycle 261 */ cyclic(int i)262 public boolean cyclic(int i) { 263 return searcher.cyclic(i); 264 } 265 266 /** 267 * Construct a set of vertices which belong to any cycle (ring). 268 * 269 * @return cyclic vertices 270 */ cyclic()271 public int[] cyclic() { 272 return searcher.cyclic(); 273 } 274 275 /** 276 * Construct the sets of vertices which belong to isolated rings. 277 * 278 * <blockquote><pre>{@code 279 * IAtomContainer biphenyl = ...; 280 * RingSearch ringSearch = new RingSearch(biphenyl); 281 * 282 * int[][] isolated = ringSearch.isolated(); 283 * isolated.length; // 2 isolated rings in biphenyl 284 * 285 * isolated[0].length; // 6 vertices in one benzene 286 * isolated[1].length; // 6 vertices in the other benzene 287 * 288 * }</pre></blockquote> 289 * 290 * @return array of isolated fragments, defined by the vertices in the 291 * fragment 292 */ isolated()293 public int[][] isolated() { 294 return searcher.isolated(); 295 } 296 297 /** 298 * Construct the sets of vertices which belong to fused ring systems. 299 * 300 * <blockquote><pre>{@code 301 * IAtomContainer mol = ...; 302 * RingSearch ringSearch = new RingSearch(mol); 303 * 304 * int[][] fused = ringSearch.fused(); 305 * fused.length; // e.g. 3 separate fused ring systems 306 * 307 * fused[0].length; // e.g. 6 vertices in the first system 308 * fused[1].length; // e.g. 10 vertices in the second system 309 * fused[2].length; // e.g. 4 vertices in the third system 310 * 311 * }</pre></blockquote> 312 * 313 * @return array of fused fragments, defined by the vertices in the 314 * fragment 315 */ fused()316 public int[][] fused() { 317 return searcher.fused(); 318 } 319 320 /** 321 * Extract the cyclic atom and bond fragments of the container. Bonds which 322 * join two different isolated/fused cycles (e.g. biphenyl) are not be 323 * included. 324 * 325 * @return a new container with only the cyclic atoms and bonds 326 * @see org.openscience.cdk.graph.SpanningTree#getCyclicFragmentsContainer() 327 */ ringFragments()328 public IAtomContainer ringFragments() { 329 330 int[] vertices = cyclic(); 331 332 int n = vertices.length; 333 334 IAtom[] atoms = new IAtom[n]; 335 List<IBond> bonds = new ArrayList<IBond>(); 336 337 for (int i = 0; i < vertices.length; i++) { 338 atoms[i] = container.getAtom(vertices[i]); 339 } 340 341 for (IBond bond : container.bonds()) { 342 343 IAtom either = bond.getBegin(); 344 IAtom other = bond.getEnd(); 345 346 int u = container.indexOf(either); 347 int v = container.indexOf(other); 348 349 // add the bond if the vertex colors match 350 if (searcher.cyclic(u, v)) bonds.add(bond); 351 } 352 353 IChemObjectBuilder builder = container.getBuilder(); 354 IAtomContainer fragment = builder.newInstance(IAtomContainer.class, 0, 0, 0, 0); 355 356 fragment.setAtoms(atoms); 357 fragment.setBonds(bonds.toArray(new IBond[bonds.size()])); 358 359 return fragment; 360 361 } 362 363 /** 364 * Determines whether the two vertex colors match. This method provides the 365 * conditional as to whether to include a bond in the construction of the 366 * {@link #ringFragments()}. 367 * 368 * @param eitherColor either vertex color 369 * @param otherColor other vertex color 370 * @return whether the two vertex colours match 371 */ match(int eitherColor, int otherColor)372 static boolean match(int eitherColor, int otherColor) { 373 return (eitherColor != -1 && otherColor != -1) 374 && (eitherColor == otherColor || (eitherColor == 0 || otherColor == 0)); 375 } 376 377 /** 378 * Construct a list of {@link IAtomContainer}s each of which only contains a 379 * single isolated ring. A ring is consider isolated if it does not share 380 * any bonds with another ring. By this definition each ring of a spiro ring 381 * system is considered isolated. The atoms are <b>not</b> arranged 382 * sequential. 383 * 384 * @return list of isolated ring fragments 385 * @see #isolated() 386 */ isolatedRingFragments()387 public List<IAtomContainer> isolatedRingFragments() { 388 return toFragments(isolated()); 389 } 390 391 /** 392 * Construct a list of {@link IAtomContainer}s which only contain fused 393 * rings. A ring is consider fused if it shares any bonds with another ring. 394 * By this definition bridged ring systems are also included. The atoms are 395 * <b>not</b> arranged sequential. 396 * 397 * @return list of fused ring fragments 398 * @see #fused() 399 */ fusedRingFragments()400 public List<IAtomContainer> fusedRingFragments() { 401 return toFragments(fused()); 402 } 403 404 /** 405 * Utility method for creating the fragments for the fused/isolated sets 406 * 407 * @param verticesList 2D array of vertices (rows=n fragments) 408 * @return the vertices converted to an atom container 409 * @see #toFragment(int[]) 410 * @see #fusedRingFragments() 411 * @see #isolatedRingFragments() 412 */ toFragments(int[][] verticesList)413 private List<IAtomContainer> toFragments(int[][] verticesList) { 414 List<IAtomContainer> fragments = new ArrayList<IAtomContainer>(); 415 for (int[] vertices : verticesList) { 416 fragments.add(toFragment(vertices)); 417 } 418 return fragments; 419 } 420 421 /** 422 * Utility method for creating a fragment from an array of vertices 423 * 424 * @param vertices array of vertices length=cycle weight, values 0 ... 425 * nAtoms 426 * @return atom container only containing the specified atoms (and bonds) 427 */ toFragment(int[] vertices)428 private IAtomContainer toFragment(int[] vertices) { 429 430 int n = vertices.length; 431 432 Set<IAtom> atoms = new HashSet<IAtom>(n > 3 ? n + 1 + n / 3 : n); 433 List<IBond> bonds = new ArrayList<IBond>(); 434 435 // fill the atom set 436 for (int v : vertices) { 437 atoms.add(container.getAtom(v)); 438 } 439 440 // include bonds that have both atoms in the atoms set 441 for (IBond bond : container.bonds()) { 442 IAtom either = bond.getBegin(); 443 IAtom other = bond.getEnd(); 444 if (atoms.contains(either) && atoms.contains(other)) { 445 bonds.add(bond); 446 } 447 } 448 449 IAtomContainer fragment = container.getBuilder().newInstance(IAtomContainer.class, 0, 0, 0, 0); 450 451 fragment.setAtoms(atoms.toArray(new IAtom[n])); 452 fragment.setBonds(bonds.toArray(new IBond[bonds.size()])); 453 454 return fragment; 455 } 456 } 457