Beispiel #1
0
func TestCommunityQ(t *testing.T) {
	for _, test := range communityQTests {
		g := simple.NewUndirectedGraph(0, 0)
		for u, e := range test.g {
			// Add nodes that are not defined by an edge.
			if !g.Has(simple.Node(u)) {
				g.AddNode(simple.Node(u))
			}
			for v := range e {
				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
			}
		}
		for _, structure := range test.structures {
			communities := make([][]graph.Node, len(structure.memberships))
			for i, c := range structure.memberships {
				for n := range c {
					communities[i] = append(communities[i], simple.Node(n))
				}
			}
			got := Q(g, communities, structure.resolution)
			if !floats.EqualWithinAbsOrRel(got, structure.want, structure.tol, structure.tol) && math.IsNaN(got) != math.IsNaN(structure.want) {
				for _, c := range communities {
					sort.Sort(ordered.ByID(c))
				}
				t.Errorf("unexpected Q value for %q %v: got: %v want: %v",
					test.name, communities, got, structure.want)
			}
		}
	}
}
Beispiel #2
0
func TestDenseLists(t *testing.T) {
	dg := NewDirectedMatrix(15, 1, 0, math.Inf(1))
	nodes := dg.Nodes()

	if len(nodes) != 15 {
		t.Fatalf("Wrong number of nodes")
	}

	sort.Sort(ordered.ByID(nodes))

	for i, node := range dg.Nodes() {
		if i != node.ID() {
			t.Errorf("Node list doesn't return properly id'd nodes")
		}
	}

	edges := dg.Edges()
	if len(edges) != 15*14 {
		t.Errorf("Improper number of edges for passable dense graph")
	}

	dg.RemoveEdge(Edge{F: Node(12), T: Node(11)})
	edges = dg.Edges()
	if len(edges) != (15*14)-1 {
		t.Errorf("Removing edge didn't affect edge listing properly")
	}
}
// NewDirectedMatrixFrom creates a directed dense graph with the given nodes.
// The IDs of the nodes must be contiguous from 0 to len(nodes)-1, but may
// be in any order. If IDs are not contiguous NewDirectedMatrixFrom will panic.
// All edges are initialized with the weight given by init. The self parameter
// specifies the cost of self connection, and absent specifies the weight
// returned for absent edges.
func NewDirectedMatrixFrom(nodes []graph.Node, init, self, absent float64) *DirectedMatrix {
	sort.Sort(ordered.ByID(nodes))
	for i, n := range nodes {
		if i != n.ID() {
			panic("simple: non-contiguous node IDs")
		}
	}
	g := NewDirectedMatrix(len(nodes), init, self, absent)
	g.nodes = nodes
	return g
}
Beispiel #4
0
func TestReduceQConsistency(t *testing.T) {
tests:
	for _, test := range communityQTests {
		g := simple.NewUndirectedGraph(0, 0)
		for u, e := range test.g {
			// Add nodes that are not defined by an edge.
			if !g.Has(simple.Node(u)) {
				g.AddNode(simple.Node(u))
			}
			for v := range e {
				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
			}
		}

		for _, structure := range test.structures {
			if math.IsNaN(structure.want) {
				continue tests
			}

			communities := make([][]graph.Node, len(structure.memberships))
			for i, c := range structure.memberships {
				for n := range c {
					communities[i] = append(communities[i], simple.Node(n))
				}
				sort.Sort(ordered.ByID(communities[i]))
			}

			gQ := Q(g, communities, structure.resolution)
			gQnull := Q(g, nil, 1)

			cg0 := reduce(g, nil)
			cg0Qnull := Q(cg0, cg0.Structure(), 1)
			if !floats.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
				t.Errorf("disgagreement between null Q from method: %v and function: %v", cg0Qnull, gQnull)
			}
			cg0Q := Q(cg0, communities, structure.resolution)
			if !floats.EqualWithinAbsOrRel(gQ, cg0Q, structure.tol, structure.tol) {
				t.Errorf("unexpected Q result after initial conversion: got: %v want :%v", gQ, cg0Q)
			}

			cg1 := reduce(cg0, communities)
			cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
			if !floats.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
				t.Errorf("unexpected Q result after initial condensation: got: %v want :%v", gQ, cg1Q)
			}
		}
	}
}
Beispiel #5
0
func TestMoveLocal(t *testing.T) {
	for _, test := range localMoveTests {
		g := simple.NewUndirectedGraph(0, 0)
		for u, e := range test.g {
			// Add nodes that are not defined by an edge.
			if !g.Has(simple.Node(u)) {
				g.AddNode(simple.Node(u))
			}
			for v := range e {
				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
			}
		}

		for _, structure := range test.structures {
			communities := make([][]graph.Node, len(structure.memberships))
			for i, c := range structure.memberships {
				for n := range c {
					communities[i] = append(communities[i], simple.Node(n))
				}
				sort.Sort(ordered.ByID(communities[i]))
			}

			r := reduce(reduce(g, nil), communities)

			l := newLocalMover(r, r.communities, structure.resolution)
			for _, n := range structure.targetNodes {
				dQ, dst, src := l.deltaQ(n)
				if dQ > 0 {
					before := Q(r, l.communities, structure.resolution)
					l.move(dst, src)
					after := Q(r, l.communities, structure.resolution)
					want := after - before
					if !floats.EqualWithinAbsOrRel(dQ, want, structure.tol, structure.tol) {
						t.Errorf("unexpected deltaQ: got: %v want: %v", dQ, want)
					}
				}
			}
		}
	}
}
Beispiel #6
0
// Sort performs a topological sort of the directed graph g returning the 'from' to 'to'
// sort order. If a topological ordering is not possible, an Unorderable error is returned
// listing cyclic components in g with each cyclic component's members sorted by ID. When
// an Unorderable error is returned, each cyclic component's topological position within
// the sorted nodes is marked with a nil graph.Node.
func Sort(g graph.Directed) (sorted []graph.Node, err error) {
	sccs := TarjanSCC(g)
	sorted = make([]graph.Node, 0, len(sccs))
	var sc Unorderable
	for _, s := range sccs {
		if len(s) != 1 {
			sort.Sort(ordered.ByID(s))
			sc = append(sc, s)
			sorted = append(sorted, nil)
			continue
		}
		sorted = append(sorted, s[0])
	}
	if sc != nil {
		for i, j := 0, len(sc)-1; i < j; i, j = i+1, j-1 {
			sc[i], sc[j] = sc[j], sc[i]
		}
		err = sc
	}
	reverse(sorted)
	return sorted, err
}
Beispiel #7
0
func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool) error {
	nodes := g.Nodes()
	sort.Sort(ordered.ByID(nodes))

	p.buf.WriteString(p.prefix)
	if needsIndent {
		for i := 0; i < p.depth; i++ {
			p.buf.WriteString(p.indent)
		}
	}
	_, isDirected := g.(graph.Directed)
	if isSubgraph {
		p.buf.WriteString("sub")
	} else if isDirected {
		p.buf.WriteString("di")
	}
	p.buf.WriteString("graph")

	if name == "" {
		if g, ok := g.(Graph); ok {
			name = g.DOTID()
		}
	}
	if name != "" {
		p.buf.WriteByte(' ')
		p.buf.WriteString(name)
	}

	p.openBlock(" {")
	if a, ok := g.(Attributers); ok {
		p.writeAttributeComplex(a)
	}
	if s, ok := g.(Structurer); ok {
		for _, g := range s.Structure() {
			_, subIsDirected := g.(graph.Directed)
			if subIsDirected != isDirected {
				return errors.New("dot: mismatched graph type")
			}
			p.buf.WriteByte('\n')
			p.print(g, g.DOTID(), true, true)
		}
	}

	havePrintedNodeHeader := false
	for _, n := range nodes {
		if s, ok := n.(Subgrapher); ok {
			// If the node is not linked to any other node
			// the graph needs to be written now.
			if len(g.From(n)) == 0 {
				g := s.Subgraph()
				_, subIsDirected := g.(graph.Directed)
				if subIsDirected != isDirected {
					return errors.New("dot: mismatched graph type")
				}
				if !havePrintedNodeHeader {
					p.newline()
					p.buf.WriteString("// Node definitions.")
					havePrintedNodeHeader = true
				}
				p.newline()
				p.print(g, graphID(g, n), false, true)
			}
			continue
		}
		if !havePrintedNodeHeader {
			p.newline()
			p.buf.WriteString("// Node definitions.")
			havePrintedNodeHeader = true
		}
		p.newline()
		p.writeNode(n)
		if a, ok := n.(Attributer); ok {
			p.writeAttributeList(a)
		}
		p.buf.WriteByte(';')
	}

	havePrintedEdgeHeader := false
	for _, n := range nodes {
		to := g.From(n)
		sort.Sort(ordered.ByID(to))
		for _, t := range to {
			if isDirected {
				if p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] {
					continue
				}
				p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] = true
			} else {
				if p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] {
					continue
				}
				p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] = true
				p.visited[edge{inGraph: name, from: t.ID(), to: n.ID()}] = true
			}

			if !havePrintedEdgeHeader {
				p.buf.WriteByte('\n')
				p.buf.WriteString(strings.TrimRight(p.prefix, " \t\xa0")) // Trim whitespace suffix.
				p.newline()
				p.buf.WriteString("// Edge definitions.")
				havePrintedEdgeHeader = true
			}
			p.newline()

			if s, ok := n.(Subgrapher); ok {
				g := s.Subgraph()
				_, subIsDirected := g.(graph.Directed)
				if subIsDirected != isDirected {
					return errors.New("dot: mismatched graph type")
				}
				p.print(g, graphID(g, n), false, true)
			} else {
				p.writeNode(n)
			}
			e, edgeIsPorter := g.Edge(n, t).(Porter)
			if edgeIsPorter {
				p.writePorts(e.FromPort())
			}

			if isDirected {
				p.buf.WriteString(" -> ")
			} else {
				p.buf.WriteString(" -- ")
			}

			if s, ok := t.(Subgrapher); ok {
				g := s.Subgraph()
				_, subIsDirected := g.(graph.Directed)
				if subIsDirected != isDirected {
					return errors.New("dot: mismatched graph type")
				}
				p.print(g, graphID(g, t), false, true)
			} else {
				p.writeNode(t)
			}
			if edgeIsPorter {
				p.writePorts(e.ToPort())
			}

			if a, ok := g.Edge(n, t).(Attributer); ok {
				p.writeAttributeList(a)
			}

			p.buf.WriteByte(';')
		}
	}
	p.closeBlock("}")

	return nil
}
Beispiel #8
0
// Duplication constructs a graph in the destination, dst, of order n. New nodes
// are created by duplicating an existing node and all its edges. Each new edge is
// deleted with probability delta. Additional edges are added between the new node
// and existing nodes with probability alpha/|V|. An exception to this addition
// rule is made for the parent node when sigma is not NaN; in this case an edge is
// created with probability sigma. With the exception of the sigma parameter, this
// corresponds to the completely correlated case in doi:10.1016/S0022-5193(03)00028-6.
// If src is not nil it is used as the random source, otherwise rand.Float64 is used.
func Duplication(dst UndirectedMutator, n int, delta, alpha, sigma float64, src *rand.Rand) error {
	// As described in doi:10.1016/S0022-5193(03)00028-6 but
	// also clarified in doi:10.1186/gb-2007-8-4-r51.

	if delta < 0 || delta > 1 {
		return fmt.Errorf("gen: bad delta: delta=%v", delta)
	}
	if alpha <= 0 || alpha > 1 {
		return fmt.Errorf("gen: bad alpha: alpha=%v", alpha)
	}
	if sigma < 0 || sigma > 1 {
		return fmt.Errorf("gen: bad sigma: sigma=%v", sigma)
	}

	var (
		rnd  func() float64
		rndN func(int) int
	)
	if src == nil {
		rnd = rand.Float64
		rndN = rand.Intn
	} else {
		rnd = src.Float64
		rndN = src.Intn
	}

	nodes := dst.Nodes()
	sort.Sort(ordered.ByID(nodes))
	if len(nodes) == 0 {
		n--
		dst.AddNode(simple.Node(0))
		nodes = append(nodes, simple.Node(0))
	}
	for i := 0; i < n; i++ {
		u := nodes[rndN(len(nodes))]
		d := simple.Node(dst.NewNodeID())

		// Add the duplicate node.
		dst.AddNode(d)

		// Loop until we have connectivity
		// into the rest of the graph.
		for {
			// Add edges to parent's neigbours.
			to := dst.From(u)
			sort.Sort(ordered.ByID(to))
			for _, v := range to {
				if rnd() < delta || dst.HasEdgeBetween(v, d) {
					continue
				}
				if v.ID() < d.ID() {
					dst.SetEdge(simple.Edge{F: v, T: d, W: 1})
				} else {
					dst.SetEdge(simple.Edge{F: d, T: v, W: 1})
				}
			}

			// Add edges to old nodes.
			scaledAlpha := alpha / float64(len(nodes))
			for _, v := range nodes {
				switch v.ID() {
				case u.ID():
					if !math.IsNaN(sigma) {
						if i == 0 || rnd() < sigma {
							if v.ID() < d.ID() {
								dst.SetEdge(simple.Edge{F: v, T: d, W: 1})
							} else {
								dst.SetEdge(simple.Edge{F: d, T: v, W: 1})
							}
						}
						continue
					}
					fallthrough
				default:
					if rnd() < scaledAlpha && !dst.HasEdgeBetween(v, d) {
						if v.ID() < d.ID() {
							dst.SetEdge(simple.Edge{F: v, T: d, W: 1})
						} else {
							dst.SetEdge(simple.Edge{F: d, T: v, W: 1})
						}
					}
				}
			}

			if len(dst.From(d)) != 0 {
				break
			}
		}

		nodes = append(nodes, d)
	}

	return nil
}
Beispiel #9
0
// reduce returns a reduced graph constructed from g divided
// into the given communities. The communities value is mutated
// by the call to reduce. If communities is nil and g is a
// ReducedUndirected, it is returned unaltered.
func reduce(g graph.Undirected, communities [][]graph.Node) *ReducedUndirected {
	if communities == nil {
		if r, ok := g.(*ReducedUndirected); ok {
			return r
		}

		nodes := g.Nodes()
		// TODO(kortschak) This sort is necessary really only
		// for testing. In practice we would not be using the
		// community provided by the user for a Q calculation.
		// Probably we should use a function to map the
		// communities in the test sets to the remapped order.
		sort.Sort(ordered.ByID(nodes))
		communities = make([][]graph.Node, len(nodes))
		for i := range nodes {
			communities[i] = []graph.Node{node(i)}
		}

		weight := weightFuncFor(g)
		r := ReducedUndirected{
			nodes:       make([]community, len(nodes)),
			edges:       make([][]int, len(nodes)),
			weights:     make(map[[2]int]float64),
			communities: communities,
		}
		communityOf := make(map[int]int, len(nodes))
		for i, n := range nodes {
			r.nodes[i] = community{id: i, nodes: []graph.Node{n}}
			communityOf[n.ID()] = i
		}
		for _, u := range nodes {
			var out []int
			uid := communityOf[u.ID()]
			for _, v := range g.From(u) {
				vid := communityOf[v.ID()]
				if vid != uid {
					out = append(out, vid)
				}
				if uid < vid {
					// Only store the weight once.
					r.weights[[2]int{uid, vid}] = weight(u, v)
				}
			}
			r.edges[uid] = out
		}
		return &r
	}

	// Remove zero length communities destructively.
	var commNodes int
	for i := 0; i < len(communities); {
		comm := communities[i]
		if len(comm) == 0 {
			communities[i] = communities[len(communities)-1]
			communities[len(communities)-1] = nil
			communities = communities[:len(communities)-1]
		} else {
			commNodes += len(comm)
			i++
		}
	}

	r := ReducedUndirected{
		nodes:   make([]community, len(communities)),
		edges:   make([][]int, len(communities)),
		weights: make(map[[2]int]float64),
	}
	r.communities = make([][]graph.Node, len(communities))
	for i := range r.communities {
		r.communities[i] = []graph.Node{node(i)}
	}
	if g, ok := g.(*ReducedUndirected); ok {
		// Make sure we retain the truncated
		// community structure.
		g.communities = communities
		r.parent = g
	}
	weight := weightFuncFor(g)
	communityOf := make(map[int]int, commNodes)
	for i, comm := range communities {
		r.nodes[i] = community{id: i, nodes: comm}
		for _, n := range comm {
			communityOf[n.ID()] = i
		}
	}
	for uid, comm := range communities {
		var out []int
		for i, u := range comm {
			r.nodes[uid].weight += weight(u, u)
			for _, v := range comm[i+1:] {
				r.nodes[uid].weight += 2 * weight(u, v)
			}
			for _, v := range g.From(u) {
				vid := communityOf[v.ID()]
				found := false
				for _, e := range out {
					if e == vid {
						found = true
						break
					}
				}
				if !found && vid != uid {
					out = append(out, vid)
				}
				if uid < vid {
					// Only store the weight once.
					r.weights[[2]int{uid, vid}] += weight(u, v)
				}
			}
		}
		r.edges[uid] = out
	}
	return &r
}
Beispiel #10
0
func TestLouvain(t *testing.T) {
	const louvainIterations = 20

	for _, test := range communityQTests {
		g := simple.NewUndirectedGraph(0, 0)
		for u, e := range test.g {
			// Add nodes that are not defined by an edge.
			if !g.Has(simple.Node(u)) {
				g.AddNode(simple.Node(u))
			}
			for v := range e {
				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
			}
		}

		if test.structures[0].resolution != 1 {
			panic("bad test: expect resolution=1")
		}
		want := make([][]graph.Node, len(test.structures[0].memberships))
		for i, c := range test.structures[0].memberships {
			for n := range c {
				want[i] = append(want[i], simple.Node(n))
			}
			sort.Sort(ordered.ByID(want[i]))
		}
		sort.Sort(ordered.BySliceIDs(want))

		var (
			got   *ReducedUndirected
			bestQ = math.Inf(-1)
		)
		// Louvain is randomised so we do this to
		// ensure the level tests are consistent.
		src := rand.New(rand.NewSource(1))
		for i := 0; i < louvainIterations; i++ {
			r := Louvain(g, 1, src)
			if q := Q(r, nil, 1); q > bestQ || math.IsNaN(q) {
				bestQ = q
				got = r

				if math.IsNaN(q) {
					// Don't try again for non-connected case.
					break
				}
			}

			var qs []float64
			for p := r; p != nil; p = p.Expanded() {
				qs = append(qs, Q(p, nil, 1))
			}

			// Recovery of Q values is reversed.
			if reverse(qs); !sort.Float64sAreSorted(qs) {
				t.Errorf("Q values not monotonically increasing: %.5v", qs)
			}
		}

		gotCommunities := got.Communities()
		for _, c := range gotCommunities {
			sort.Sort(ordered.ByID(c))
		}
		sort.Sort(ordered.BySliceIDs(gotCommunities))
		if !reflect.DeepEqual(gotCommunities, want) {
			t.Errorf("unexpected community membership for %s Q=%.4v:\n\tgot: %v\n\twant:%v",
				test.name, bestQ, gotCommunities, want)
			continue
		}

		var levels []level
		for p := got; p != nil; p = p.Expanded() {
			var communities [][]graph.Node
			if p.parent != nil {
				communities = p.parent.Communities()
				for _, c := range communities {
					sort.Sort(ordered.ByID(c))
				}
				sort.Sort(ordered.BySliceIDs(communities))
			} else {
				communities = reduce(g, nil).Communities()
			}
			q := Q(p, nil, 1)
			if math.IsNaN(q) {
				// Use an equalable flag value in place of NaN.
				q = math.Inf(-1)
			}
			levels = append(levels, level{q: q, communities: communities})
		}
		if !reflect.DeepEqual(levels, test.wantLevels) {
			t.Errorf("unexpected level structure:\n\tgot: %v\n\twant:%v", levels, test.wantLevels)
		}
	}
}
Beispiel #11
0
func TestCommunityDeltaQ(t *testing.T) {
tests:
	for _, test := range communityQTests {
		g := simple.NewUndirectedGraph(0, 0)
		for u, e := range test.g {
			// Add nodes that are not defined by an edge.
			if !g.Has(simple.Node(u)) {
				g.AddNode(simple.Node(u))
			}
			for v := range e {
				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
			}
		}

		rnd := rand.New(rand.NewSource(1)).Intn
		for _, structure := range test.structures {
			communityOf := make(map[int]int)
			communities := make([][]graph.Node, len(structure.memberships))
			for i, c := range structure.memberships {
				for n := range c {
					communityOf[n] = i
					communities[i] = append(communities[i], simple.Node(n))
				}
				sort.Sort(ordered.ByID(communities[i]))
			}

			before := Q(g, communities, structure.resolution)

			l := newLocalMover(reduce(g, nil), communities, structure.resolution)
			if l == nil {
				if !math.IsNaN(before) {
					t.Errorf("unexpected nil localMover with non-NaN Q graph: Q=%.4v", before)
				}
				continue tests
			}

			// This is done to avoid run-to-run
			// variation due to map iteration order.
			sort.Sort(ordered.ByID(l.nodes))

			l.shuffle(rnd)

			for _, target := range l.nodes {
				got, gotDst, gotSrc := l.deltaQ(target)

				want, wantDst := math.Inf(-1), -1
				migrated := make([][]graph.Node, len(structure.memberships))
				for i, c := range structure.memberships {
					for n := range c {
						if n == target.ID() {
							continue
						}
						migrated[i] = append(migrated[i], simple.Node(n))
					}
					sort.Sort(ordered.ByID(migrated[i]))
				}

				for i, c := range structure.memberships {
					if i == communityOf[target.ID()] {
						continue
					}
					connected := false
					for n := range c {
						if g.HasEdgeBetween(simple.Node(n), target) {
							connected = true
							break
						}
					}
					if !connected {
						continue
					}
					migrated[i] = append(migrated[i], target)
					after := Q(g, migrated, structure.resolution)
					migrated[i] = migrated[i][:len(migrated[i])-1]
					if after-before > want {
						want = after - before
						wantDst = i
					}
				}

				if !floats.EqualWithinAbsOrRel(got, want, structure.tol, structure.tol) || gotDst != wantDst {
					t.Errorf("unexpected result moving n=%d in c=%d of %s/%.4v: got: %.4v,%d want: %.4v,%d"+
						"\n\t%v\n\t%v",
						target.ID(), communityOf[target.ID()], test.name, structure.resolution, got, gotDst, want, wantDst,
						communities, migrated)
				}
				if gotSrc.community != communityOf[target.ID()] {
					t.Errorf("unexpected source community index: got: %d want: %d", gotSrc, communityOf[target.ID()])
				} else if communities[gotSrc.community][gotSrc.node].ID() != target.ID() {
					wantNodeIdx := -1
					for i, n := range communities[gotSrc.community] {
						if n.ID() == target.ID() {
							wantNodeIdx = i
							break
						}
					}
					t.Errorf("unexpected source node index: got: %d want: %d", gotSrc.node, wantNodeIdx)
				}
			}
		}
	}
}