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