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 java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.BitSet; 28 import java.util.List; 29 30 /** 31 * CyclicVertexSearch for graphs with more then 64 vertices. 32 * 33 * @author John May 34 * @cdk.module core 35 */ 36 class JumboCyclicVertexSearch implements CyclicVertexSearch { 37 38 /* graph representation */ 39 private final int[][] g; 40 41 /* set of known cyclic vertices */ 42 private final BitSet cyclic; 43 44 /* cycle systems as they are discovered */ 45 private List<BitSet> cycles = new ArrayList<BitSet>(1); 46 47 /* indicates if the 'cycle' at 'i' in 'cycles' is fused */ 48 private List<Boolean> fused = new ArrayList<Boolean>(1); 49 50 /* set of visited vertices */ 51 private BitSet visited; 52 53 /* the vertices in our path at a given vertex index */ 54 private BitSet[] state; 55 56 /** vertex colored by each component. */ 57 private int[] colors; 58 59 private int numCycles = 0; 60 61 /** 62 * Create a new cyclic vertex search for the provided graph. 63 * 64 * @param graph adjacency list representation of a graph 65 */ JumboCyclicVertexSearch(int[][] graph)66 JumboCyclicVertexSearch(int[][] graph) { 67 this.g = graph; 68 final int n = graph.length; 69 70 cyclic = new BitSet(n); 71 72 if (n == 0) return; 73 74 state = new BitSet[n]; 75 visited = new BitSet(n); 76 77 BitSet empty = new BitSet(n); 78 79 // start from vertex 0 80 search(0, copy(empty), copy(empty)); 81 82 // if g is a fragment we will not have visited everything 83 int v = 0; 84 while (visited.cardinality() != n) { 85 v++; 86 // each search expands to the whole fragment, as we 87 // may have fragments we need to visit 0 and then 88 // check every other vertex 89 if (!visited.get(v)) { 90 search(v, copy(empty), copy(empty)); 91 } 92 } 93 94 // allow the states to be collected 95 state = null; 96 visited = null; 97 98 } 99 100 /** 101 * Perform a depth first search from the vertex <i>v</i>. 102 * 103 * @param v vertex to search from 104 * @param prev the state before we vistaed our parent (previous state) 105 * @param curr the current state (including our parent) 106 */ search(int v, BitSet prev, BitSet curr)107 private void search(int v, BitSet prev, BitSet curr) { 108 109 state[v] = curr; // set the state before we visit v 110 curr = copy(curr); // include v in our current state (state[v] is unmodified) 111 curr.set(v); 112 visited.or(curr); // mark v as visited (or being visited) 113 114 // for each neighbor w of v 115 for (int w : g[v]) { 116 117 // if w is in our prev state we have a cycle of size >3. 118 // we don't check out current state as this will always 119 // include w - they are adjacent 120 if (prev.get(w)) { 121 numCycles++; 122 // we have a cycle, xor the state when we last visited 'w' 123 // with our current state. this set is all the vertices 124 // we visited since then 125 add(xor(state[w], curr)); 126 } 127 128 // check w hasn't been visited or isn't being visited further up the stack. 129 // this mainly stops us re-visiting the vertex we came from 130 else if (!visited.get(w)) { 131 // recursively call for the neighbor 'w' 132 search(w, state[v], curr); 133 } 134 } 135 136 } 137 138 /** Synchronisation lock. */ 139 private final Object lock = new Object(); 140 141 @Override numCycles()142 public int numCycles() { 143 return numCycles; 144 } 145 146 /** 147 * Lazily build an indexed lookup of vertex color. The vertex color 148 * indicates which cycle a given vertex belongs. If a vertex belongs to more 149 * then one cycle it is colored '0'. If a vertex belongs to no cycle it is 150 * colored '-1'. 151 * 152 * @return vertex colors 153 */ 154 @Override vertexColor()155 public int[] vertexColor() { 156 int[] result = colors; 157 if (result == null) { 158 synchronized (this) { 159 result = colors; 160 if (result == null) { 161 colors = result = buildVertexColor(); 162 } 163 } 164 } 165 return result; 166 } 167 168 /** 169 * Build an indexed lookup of vertex color. The vertex color indicates which 170 * cycle a given vertex belongs. If a vertex belongs to more then one cycle 171 * it is colored '0'. If a vertex belongs to no cycle it is colored '-1'. 172 * 173 * @return vertex colors 174 */ buildVertexColor()175 private int[] buildVertexColor() { 176 int[] color = new int[g.length]; 177 178 int n = 1; 179 Arrays.fill(color, -1); 180 for (BitSet cycle : cycles) { 181 for (int i = cycle.nextSetBit(0); i >= 0; i = cycle.nextSetBit(i + 1)) { 182 color[i] = color[i] < 0 ? n : 0; 183 } 184 n++; 185 } 186 187 return color; 188 } 189 190 /** 191 *{@inheritDoc} 192 */ 193 @Override 194 public boolean cyclic(int v) { 195 return cyclic.get(v); 196 } 197 198 /** 199 *{@inheritDoc} 200 */ 201 @Override 202 public boolean cyclic(int u, int v) { 203 204 final int[] colors = vertexColor(); 205 206 // if either vertex has no color then the edge can not 207 // be cyclic 208 if (colors[u] < 0 || colors[v] < 0) return false; 209 210 // if the vertex color is 0 it is shared between 211 // two components (i.e. spiro-rings) we need to 212 // check each component 213 if (colors[u] == 0 || colors[v] == 0) { 214 // either vertices are shared - need to do the expensive check 215 for (final BitSet cycle : cycles) { 216 if (cycle.get(u) && cycle.get(v)) return true; 217 } 218 return false; 219 } 220 221 // vertex is not shared between components 222 return colors[u] == colors[v]; 223 } 224 225 /** 226 *{@inheritDoc} 227 */ 228 @Override 229 public int[] cyclic() { 230 return toArray(cyclic); 231 } 232 233 /** 234 *{@inheritDoc} 235 */ 236 @Override 237 public int[][] isolated() { 238 List<int[]> isolated = new ArrayList<int[]>(cycles.size()); 239 for (int i = 0; i < cycles.size(); i++) { 240 if (!fused.get(i)) isolated.add(toArray(cycles.get(i))); 241 } 242 return isolated.toArray(new int[isolated.size()][]); 243 } 244 245 /** 246 *{@inheritDoc} 247 */ 248 @Override 249 public int[][] fused() { 250 List<int[]> fused = new ArrayList<int[]>(cycles.size()); 251 for (int i = 0; i < cycles.size(); i++) { 252 if (this.fused.get(i)) fused.add(toArray(cycles.get(i))); 253 } 254 return fused.toArray(new int[fused.size()][]); 255 } 256 257 /** 258 * Add the cycle vertices to our discovered cycles. The cycle is first 259 * checked to see if it is isolated (shares at most one vertex) or 260 * <i>potentially</i> fused. 261 * 262 * @param cycle newly discovered cyclic vertex set 263 */ 264 private void add(BitSet cycle) { 265 266 BitSet intersect = and(cycle, cyclic); 267 268 if (intersect.cardinality() > 1) { 269 addFused(cycle); 270 } else { 271 addIsolated(cycle); 272 } 273 274 cyclic.or(cycle); 275 276 } 277 278 /** 279 * Add an a new isolated cycle which is currently edge disjoint with all 280 * other cycles. 281 * 282 * @param cycle newly discovered cyclic vertices 283 */ 284 private void addIsolated(BitSet cycle) { 285 cycles.add(cycle); 286 fused.add(false); 287 } 288 289 /** 290 * Adds a <i>potentially</i> fused cycle. If the cycle is discovered not be 291 * fused it will still be added as isolated. 292 * 293 * @param cycle vertex set of a potentially fused cycle, indicated by the 294 * set bits 295 */ 296 private void addFused(BitSet cycle) { 297 298 int i = indexOfFused(0, cycle); 299 300 if (i != -1) { 301 // add new cycle and mark as fused 302 cycles.get(i).or(cycle); 303 fused.set(i, true); 304 int j = i; 305 306 // merge other cycles we could be fused with into 'i' 307 while ((j = indexOfFused(j + 1, cycle)) != -1) { 308 cycles.get(i).or(cycles.remove(j)); 309 fused.remove(j); 310 j--; 311 } 312 } else { 313 addIsolated(cycle); 314 } 315 316 } 317 318 /** 319 * Find the next index that the <i>cycle</i> intersects with by at least two 320 * vertices. If the intersect of a vertex set with another contains more 321 * then two vertices it cannot be edge disjoint. 322 * 323 * @param start start searching from here 324 * @param cycle test whether any current cycles are fused with this one 325 * @return the index of the first fused after 'start', -1 if none 326 */ 327 private int indexOfFused(int start, BitSet cycle) { 328 for (int i = start; i < cycles.size(); i++) { 329 if (and(cycles.get(i), cycle).cardinality() > 1) { 330 return i; 331 } 332 } 333 return -1; 334 } 335 336 /** 337 * Convert the set bits of a BitSet to an int[]. 338 * 339 * @param set input with 0 or more set bits 340 * @return the bits which are set in the input 341 */ 342 static int[] toArray(BitSet set) { 343 int[] vertices = new int[set.cardinality()]; 344 int i = 0; 345 346 // fill the cyclic vertices with the bits that have been set 347 for (int v = 0; i < vertices.length; v++) { 348 if (set.get(v)) { 349 vertices[i++] = v; 350 } 351 } 352 353 return vertices; 354 } 355 356 /** 357 * XOR the to bit sets together and return the result. Neither input is 358 * modified. 359 * 360 * @param x first bit set 361 * @param y second bit set 362 * @return the XOR of the two bit sets 363 */ 364 static BitSet xor(BitSet x, BitSet y) { 365 BitSet z = copy(x); 366 z.xor(y); 367 return z; 368 } 369 370 /** 371 * AND the to bit sets together and return the result. Neither input is 372 * modified. 373 * 374 * @param x first bit set 375 * @param y second bit set 376 * @return the AND of the two bit sets 377 */ 378 static BitSet and(BitSet x, BitSet y) { 379 BitSet z = copy(x); 380 z.and(y); 381 return z; 382 } 383 384 /** 385 * Copy the original bit set. 386 * 387 * @param org input bit set 388 * @return copy of the input 389 */ 390 static BitSet copy(BitSet org) { 391 BitSet cpy = (BitSet) org.clone(); 392 return cpy; 393 } 394 395 } 396