Skip to content

Commit d99e323

Browse files
committed
FANMOD algorithm
1 parent 2c8e321 commit d99e323

2 files changed

Lines changed: 294 additions & 0 deletions

File tree

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package org.nodes.motifs;
2+
3+
import java.util.ArrayList;
4+
import java.util.Iterator;
5+
import java.util.LinkedHashSet;
6+
import java.util.LinkedList;
7+
import java.util.List;
8+
import java.util.Set;
9+
10+
import org.nodes.Graph;
11+
import org.nodes.Node;
12+
13+
import nl.peterbloem.kit.Functions;
14+
import nl.peterbloem.kit.Global;
15+
import nl.peterbloem.kit.Series;
16+
17+
/**
18+
* Enumerates all subgraphs using the FANMOD algorithm.
19+
* @author Peter
20+
*
21+
*/
22+
public class AllSubgraphs implements Iterable<Set<Integer>>
23+
{
24+
private Graph<?> data;
25+
private int size;
26+
private List<Double> probs = null;
27+
28+
29+
public AllSubgraphs(Graph<?> data, int size)
30+
{
31+
this.data = data;
32+
this.size = size;
33+
}
34+
35+
/**
36+
* @param data
37+
* @param size
38+
* @param prob The probability at each node in the search tree, that that
39+
* node will be expanded.
40+
*/
41+
public AllSubgraphs(Graph<?> data, int size, double prob)
42+
{
43+
this.data = data;
44+
this.size = size;
45+
46+
probs = new ArrayList<Double>(size);
47+
for(int i : Series.series(size))
48+
probs.add(prob);
49+
}
50+
51+
/**
52+
* Returns an iterator over lists indices of the original graph. Each
53+
* returned list represents a (weakly) connected subgraph, and all such
54+
* subgraphs are returned once.
55+
*
56+
* @return
57+
*/
58+
public Iterator<Set<Integer>> iterator()
59+
{
60+
return new Enumerator();
61+
}
62+
63+
private class Enumerator implements Iterator<Set<Integer>>
64+
{
65+
private LinkedList<State> buffer = new LinkedList<State>();
66+
67+
public Enumerator()
68+
{
69+
for(int index : Series.series(data.size()))
70+
buffer.add(new State(index));
71+
}
72+
73+
@Override
74+
public boolean hasNext()
75+
{
76+
expandBuffer();
77+
return ! buffer.isEmpty();
78+
}
79+
80+
@Override
81+
public Set<Integer> next()
82+
{
83+
expandBuffer();
84+
return buffer.pop().indices();
85+
}
86+
87+
/**
88+
* Expands the buffer until the first element is a leaf state. If that's
89+
* not possible, it returns.
90+
*/
91+
private void expandBuffer()
92+
{
93+
if(buffer.isEmpty())
94+
return;
95+
96+
while(! buffer.peek().isLeaf())
97+
{
98+
State next = buffer.pop();
99+
100+
if(probs == null || Global.random().nextDouble() < probs.get(next.indices().size() - 1))
101+
buffer.addAll(0, next.children());
102+
103+
if(buffer.isEmpty())
104+
return;
105+
}
106+
107+
assert(buffer.isEmpty() || buffer.peek().isLeaf());
108+
}
109+
110+
private class State
111+
{
112+
Set<Integer> current = new LinkedHashSet<Integer>();
113+
Set<Integer> neighbors = new LinkedHashSet<Integer>();
114+
115+
private State()
116+
{
117+
118+
}
119+
120+
public State(int start)
121+
{
122+
current.add(start);
123+
initNeighbors();
124+
}
125+
126+
private void initNeighbors()
127+
{
128+
if(isLeaf())
129+
return;
130+
131+
int max = Functions.max(current);
132+
133+
for(int index : current)
134+
for(Node<?> neighbor : data.get(index).neighbors())
135+
if(neighbor.index() > max && ! current.contains(neighbor.index()))
136+
neighbors.add(neighbor.index());
137+
}
138+
139+
/**
140+
* Adds all children of the state to the given list.
141+
*
142+
* @param list
143+
*/
144+
public List<State> children()
145+
{
146+
// * TODO: we can generate this list on the fly
147+
List<State> children = new ArrayList<State>(neighbors.size());
148+
149+
for(int neighbor : neighbors)
150+
{
151+
State state = new State();
152+
153+
state.current.addAll(this.current);
154+
state.current.add(neighbor);
155+
156+
state.initNeighbors();
157+
158+
children.add(state);
159+
}
160+
161+
return children;
162+
}
163+
164+
/**
165+
* Whether this state represents a complete subgraph (ie. it has the
166+
* required number of nodes).
167+
* @return
168+
*/
169+
public boolean isLeaf()
170+
{
171+
return current.size() == size;
172+
}
173+
174+
public Set<Integer> indices()
175+
{
176+
return current;
177+
}
178+
179+
public String toString()
180+
{
181+
return current + "_" + neighbors;
182+
}
183+
}
184+
}
185+
186+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.nodes.motifs;
2+
3+
import static org.junit.Assert.*;
4+
5+
import java.util.ArrayList;
6+
import java.util.LinkedHashSet;
7+
import java.util.List;
8+
import java.util.Set;
9+
10+
import org.junit.Test;
11+
import org.nodes.DGraph;
12+
import org.nodes.DNode;
13+
import org.nodes.MapDTGraph;
14+
import org.nodes.MapUTGraph;
15+
import org.nodes.UGraph;
16+
import org.nodes.UNode;
17+
18+
public class EnumSubgraphsTest {
19+
20+
/**
21+
* Test the graph from figure 2 of the paper
22+
*/
23+
@Test
24+
public void test()
25+
{
26+
UGraph<String> graph = new MapUTGraph<String, String>();
27+
28+
UNode<String> n0 = graph.add(null);
29+
UNode<String> n1 = graph.add(null);
30+
UNode<String> n2 = graph.add(null);
31+
UNode<String> n3 = graph.add(null);
32+
UNode<String> n4 = graph.add(null);
33+
UNode<String> n5 = graph.add(null);
34+
UNode<String> n6 = graph.add(null);
35+
UNode<String> n7 = graph.add(null);
36+
UNode<String> n8 = graph.add(null);
37+
38+
n0.connect(n1);
39+
n1.connect(n2);
40+
n2.connect(n0);
41+
42+
n0.connect(n3);
43+
n0.connect(n4);
44+
45+
n1.connect(n5);
46+
n1.connect(n6);
47+
48+
n2.connect(n7);
49+
n2.connect(n8);
50+
51+
AllSubgraphs as = new AllSubgraphs(graph, 3);
52+
53+
List<Set<Integer>> subgraphList = new ArrayList<Set<Integer>>();
54+
Set<Set<Integer>> subgraphSet = new LinkedHashSet<Set<Integer>>();
55+
56+
for(Set<Integer> sub : as)
57+
{
58+
subgraphList.add(sub);
59+
subgraphSet.add(sub);
60+
}
61+
62+
assertEquals(16, subgraphList.size());
63+
assertEquals(subgraphSet.size(), subgraphList.size());
64+
}
65+
66+
@Test
67+
public void testD()
68+
{
69+
DGraph<String> graph = new MapDTGraph<String, String>();
70+
71+
DNode<String> n0 = graph.add(null);
72+
DNode<String> n1 = graph.add(null);
73+
DNode<String> n2 = graph.add(null);
74+
DNode<String> n3 = graph.add(null);
75+
DNode<String> n4 = graph.add(null);
76+
DNode<String> n5 = graph.add(null);
77+
DNode<String> n6 = graph.add(null);
78+
DNode<String> n7 = graph.add(null);
79+
DNode<String> n8 = graph.add(null);
80+
81+
n0.connect(n1);
82+
n1.connect(n2);
83+
n2.connect(n0);
84+
85+
n0.connect(n3);
86+
n0.connect(n4);
87+
88+
n1.connect(n5);
89+
n1.connect(n6);
90+
91+
n2.connect(n7);
92+
n2.connect(n8);
93+
94+
AllSubgraphs as = new AllSubgraphs(graph, 3);
95+
96+
List<Set<Integer>> subgraphList = new ArrayList<Set<Integer>>();
97+
Set<Set<Integer>> subgraphSet = new LinkedHashSet<Set<Integer>>();
98+
99+
for(Set<Integer> sub : as)
100+
{
101+
subgraphList.add(sub);
102+
subgraphSet.add(sub);
103+
}
104+
105+
assertEquals(16, subgraphList.size());
106+
assertEquals(subgraphSet.size(), subgraphList.size());
107+
}
108+
}

0 commit comments

Comments
 (0)