graph/...: remove Weight method from Edge

This commit is contained in:
kortschak
2017-08-22 13:55:14 +09:30
parent ffa13e8edb
commit 7ba61f0ead
48 changed files with 1939 additions and 842 deletions

View File

@@ -10,8 +10,8 @@ import (
"sort"
"testing"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/internal/ordered"
"gonum.org/v1/gonum/graph/simple"
)
@@ -24,10 +24,10 @@ func ExampleProfile_simple() {
// |/ \|
// 1 5
//
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
for u, e := range smallDumbell {
for v := range e {
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
@@ -56,27 +56,27 @@ func ExampleProfile_simple() {
// Low:3.5 High:10 Score:0 Communities:[[0] [1] [2] [3] [4] [5]] Q=-0.607
}
var friends, enemies *simple.UndirectedGraph
var friends, enemies *simple.WeightedUndirectedGraph
func init() {
friends = simple.NewUndirectedGraph(0, 0)
friends = simple.NewWeightedUndirectedGraph(0, 0)
for u, e := range middleEast.friends {
// Ensure unconnected nodes are included.
if !friends.Has(simple.Node(u)) {
friends.AddNode(simple.Node(u))
}
for v := range e {
friends.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
friends.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
enemies = simple.NewUndirectedGraph(0, 0)
enemies = simple.NewWeightedUndirectedGraph(0, 0)
for u, e := range middleEast.enemies {
// Ensure unconnected nodes are included.
if !enemies.Has(simple.Node(u)) {
enemies.AddNode(simple.Node(u))
}
for v := range e {
enemies.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: -1})
enemies.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: -1})
}
}
}
@@ -112,8 +112,8 @@ func ExampleProfile_multiplex() {
// Output:
// Low:0.1 High:0.72 Score:26 Communities:[[0] [1 7 9 12] [2 8 11] [3 4 5 10] [6]] Q=[24.7 1.97]
// Low:0.72 High:1.1 Score:24 Communities:[[0 6] [1 7 9 12] [2 8 11] [3 4 5 10]] Q=[16.9 14.1]
// Low:1.1 High:1.2 Score:18 Communities:[[0 2 6 11] [1 7 9 12] [3 4 5 8 10]] Q=[9.16 25.1]
// Low:1.2 High:1.6 Score:10 Communities:[[0 3 4 5 6 10] [1 7 9 12] [2 8 11]] Q=[11.4 24.1]
// Low:1.1 High:1.1 Score:18 Communities:[[0 2 6 11] [1 7 9 12] [3 4 5 8 10]] Q=[9.16 25.1]
// Low:1.1 High:1.6 Score:10 Communities:[[0 3 4 5 6 10] [1 7 9 12] [2 8 11]] Q=[11.5 23.9]
// Low:1.6 High:1.6 Score:8 Communities:[[0 1 6 7 9 12] [2 8 11] [3 4 5 10]] Q=[5.56 39.8]
// Low:1.6 High:1.8 Score:2 Communities:[[0 2 3 4 5 6 10] [1 7 8 9 11 12]] Q=[-1.82 48.6]
// Low:1.8 High:2.3 Score:-6 Communities:[[0 2 3 4 5 6 8 10 11] [1 7 9 12]] Q=[-5 57.5]
@@ -124,77 +124,119 @@ func ExampleProfile_multiplex() {
func TestProfileUndirected(t *testing.T) {
for _, test := range communityUndirectedQTests {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
fn := ModularScore(g, Weight, 10, nil)
p, err := Profile(fn, true, 1e-3, 0.1, 10)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
testProfileUndirected(t, test, g)
}
}
func TestProfileWeightedUndirected(t *testing.T) {
for _, test := range communityUndirectedQTests {
g := simple.NewWeightedUndirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
const tries = 1000
for i, d := range p {
var score float64
for i := 0; i < tries; i++ {
score, _ = fn(d.Low)
if score >= d.Score {
break
}
}
if score < d.Score {
t.Errorf("%s: failed to recover low end score: got: %v want: %v", test.name, score, d.Score)
}
if i != 0 && d.Score >= p[i-1].Score {
t.Errorf("%s: not monotonically decreasing: %v -> %v", test.name, p[i-1], d)
testProfileUndirected(t, test, g)
}
}
func testProfileUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
fn := ModularScore(g, Weight, 10, nil)
p, err := Profile(fn, true, 1e-3, 0.1, 10)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
const tries = 1000
for i, d := range p {
var score float64
for i := 0; i < tries; i++ {
score, _ = fn(d.Low)
if score >= d.Score {
break
}
}
if score < d.Score {
t.Errorf("%s: failed to recover low end score: got: %v want: %v", test.name, score, d.Score)
}
if i != 0 && d.Score >= p[i-1].Score {
t.Errorf("%s: not monotonically decreasing: %v -> %v", test.name, p[i-1], d)
}
}
}
func TestProfileDirected(t *testing.T) {
for _, test := range communityDirectedQTests {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
fn := ModularScore(g, Weight, 10, nil)
p, err := Profile(fn, true, 1e-3, 0.1, 10)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
testProfileDirected(t, test, g)
}
}
func TestProfileWeightedDirected(t *testing.T) {
for _, test := range communityDirectedQTests {
g := simple.NewWeightedDirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
const tries = 1000
for i, d := range p {
var score float64
for i := 0; i < tries; i++ {
score, _ = fn(d.Low)
if score >= d.Score {
break
}
}
if score < d.Score {
t.Errorf("%s: failed to recover low end score: got: %v want: %v", test.name, score, d.Score)
}
if i != 0 && d.Score >= p[i-1].Score {
t.Errorf("%s: not monotonically decreasing: %v -> %v", test.name, p[i-1], d)
testProfileDirected(t, test, g)
}
}
func testProfileDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
fn := ModularScore(g, Weight, 10, nil)
p, err := Profile(fn, true, 1e-3, 0.1, 10)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
const tries = 1000
for i, d := range p {
var score float64
for i := 0; i < tries; i++ {
score, _ = fn(d.Low)
if score >= d.Score {
break
}
}
if score < d.Score {
t.Errorf("%s: failed to recover low end score: got: %v want: %v", test.name, score, d.Score)
}
if i != 0 && d.Score >= p[i-1].Score {
t.Errorf("%s: not monotonically decreasing: %v -> %v", test.name, p[i-1], d)
}
}
}

View File

@@ -356,7 +356,8 @@ const (
)
// positiveWeightFuncFor returns a constructed weight function for the
// positively weighted g.
// positively weighted g. Unweighted graphs have unit weight for existing
// edges.
func positiveWeightFuncFor(g graph.Graph) func(x, y graph.Node) float64 {
if wg, ok := g.(graph.Weighted); ok {
return func(x, y graph.Node) float64 {
@@ -375,16 +376,13 @@ func positiveWeightFuncFor(g graph.Graph) func(x, y graph.Node) float64 {
if e == nil {
return 0
}
w := e.Weight()
if w < 0 {
panic(negativeWeight)
}
return w
return 1
}
}
// negativeWeightFuncFor returns a constructed weight function for the
// negatively weighted g.
// negatively weighted g. Unweighted graphs have unit weight for existing
// edges.
func negativeWeightFuncFor(g graph.Graph) func(x, y graph.Node) float64 {
if wg, ok := g.(graph.Weighted); ok {
return func(x, y graph.Node) float64 {
@@ -403,11 +401,7 @@ func negativeWeightFuncFor(g graph.Graph) func(x, y graph.Node) float64 {
if e == nil {
return 0
}
w := e.Weight()
if w > 0 {
panic(positiveWeight)
}
return -w
return 1
}
}

View File

@@ -647,12 +647,32 @@ func TestLouvainDirectedMultiplex(t *testing.T) {
}
func TestNonContiguousDirectedMultiplex(t *testing.T) {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(4), T: simple.Node(5)},
} {
g.SetEdge(e)
}
func() {
defer func() {
r := recover()
if r != nil {
t.Error("unexpected panic with non-contiguous ID range")
}
}()
ModularizeMultiplex(DirectedLayers{g}, nil, nil, true, nil)
}()
}
func TestNonContiguousWeightedDirectedMultiplex(t *testing.T) {
g := simple.NewWeightedDirectedGraph(0, 0)
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 1},
{F: simple.Node(4), T: simple.Node(5), W: 1},
} {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
func() {
@@ -677,7 +697,7 @@ func directedMultiplexFrom(raw []layer) (DirectedLayers, []float64, error) {
var layers []graph.Directed
var weights []float64
for _, l := range raw {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewWeightedDirectedGraph(0, 0)
for u, e := range l.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -688,7 +708,7 @@ func directedMultiplexFrom(raw []layer) (DirectedLayers, []float64, error) {
if l.edgeWeight != 0 {
w = l.edgeWeight
}
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: w})
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: w})
}
}
layers = append(layers, g)

View File

@@ -17,13 +17,15 @@ import (
"gonum.org/v1/gonum/graph/simple"
)
var communityDirectedQTests = []struct {
type communityDirectedQTest struct {
name string
g []intset
structures []structure
wantLevels []level
}{
}
var communityDirectedQTests = []communityDirectedQTest{
{
name: "simple_directed",
g: simpleDirected,
@@ -199,194 +201,258 @@ var communityDirectedQTests = []struct {
func TestCommunityQDirected(t *testing.T) {
for _, test := range communityDirectedQTests {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
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))
}
testCommunityQDirected(t, test, g)
}
}
func TestCommunityQWeightedDirected(t *testing.T) {
for _, test := range communityDirectedQTests {
g := simple.NewWeightedDirectedGraph(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))
}
got := Q(g, communities, structure.resolution)
if !floats.EqualWithinAbsOrRel(got, structure.want, structure.tol, structure.tol) && !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)
for v := range e {
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
testCommunityQDirected(t, test, g)
}
}
func testCommunityQDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
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(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)
}
}
}
func TestCommunityDeltaQDirected(t *testing.T) {
tests:
for _, test := range communityDirectedQTests {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
rnd := rand.New(rand.NewSource(1)).Intn
for _, structure := range test.structures {
communityOf := make(map[int64]int)
communities := make([][]graph.Node, len(structure.memberships))
testCommunityDeltaQDirected(t, test, g)
}
}
func TestCommunityDeltaQWeightedDirected(t *testing.T) {
for _, test := range communityDirectedQTests {
g := simple.NewWeightedDirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
testCommunityDeltaQDirected(t, test, g)
}
}
func testCommunityDeltaQDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
rnd := rand.New(rand.NewSource(1)).Intn
for _, structure := range test.structures {
communityOf := make(map[int64]int)
communities := make([][]graph.Node, len(structure.memberships))
for i, c := range structure.memberships {
for n := range c {
n := int64(n)
communityOf[n] = i
communities[i] = append(communities[i], simple.Node(n))
}
sort.Sort(ordered.ByID(communities[i]))
}
before := Q(g, communities, structure.resolution)
l := newDirectedLocalMover(reduceDirected(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)
}
return
}
// 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 {
n := int64(n)
communityOf[n] = i
communities[i] = append(communities[i], simple.Node(n))
}
sort.Sort(ordered.ByID(communities[i]))
}
before := Q(g, communities, structure.resolution)
l := newDirectedLocalMover(reduceDirected(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 {
n := int64(n)
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()] {
if n == 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
}
migrated[i] = append(migrated[i], simple.Node(n))
}
sort.Sort(ordered.ByID(migrated[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)
for i, c := range structure.memberships {
if i == communityOf[target.ID()] {
continue
}
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
}
connected := false
for n := range c {
if g.HasEdgeBetween(simple.Node(n), target) {
connected = true
break
}
t.Errorf("unexpected source node index: got: %d want: %d", gotSrc.node, wantNodeIdx)
}
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)
}
}
}
}
func TestReduceQConsistencyDirected(t *testing.T) {
tests:
for _, test := range communityDirectedQTests {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
for _, structure := range test.structures {
if math.IsNaN(structure.want) {
continue tests
}
testReduceQConsistencyDirected(t, test, g)
}
}
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]))
func TestReduceQConsistencyWeightedDirected(t *testing.T) {
for _, test := range communityDirectedQTests {
g := simple.NewWeightedDirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
gQ := Q(g, communities, structure.resolution)
gQnull := Q(g, nil, 1)
testReduceQConsistencyDirected(t, test, g)
}
}
cg0 := reduceDirected(g, nil)
cg0Qnull := Q(cg0, cg0.Structure(), 1)
if !floats.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
t.Errorf("disagreement 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 reduction: got: %v want :%v", cg0Q, gQ)
}
func testReduceQConsistencyDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
for _, structure := range test.structures {
if math.IsNaN(structure.want) {
return
}
cg1 := reduceDirected(cg0, communities)
cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
if !floats.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ)
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 := reduceDirected(g, nil)
cg0Qnull := Q(cg0, cg0.Structure(), 1)
if !floats.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
t.Errorf("disagreement 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 reduction: got: %v want :%v", cg0Q, gQ)
}
cg1 := reduceDirected(cg0, communities)
cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
if !floats.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ)
}
}
}
var localDirectedMoveTests = []struct {
type localDirectedMoveTest struct {
name string
g []intset
structures []moveStructures
}{
}
var localDirectedMoveTests = []localDirectedMoveTest{
{
name: "blondel",
g: blondel,
@@ -431,39 +497,60 @@ var localDirectedMoveTests = []struct {
func TestMoveLocalDirected(t *testing.T) {
for _, test := range localDirectedMoveTests {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
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]))
testMoveLocalDirected(t, test, g)
}
}
func TestMoveLocalWeightedDirected(t *testing.T) {
for _, test := range localDirectedMoveTests {
g := simple.NewWeightedDirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
r := reduceDirected(reduceDirected(g, nil), communities)
testMoveLocalDirected(t, test, g)
}
}
l := newDirectedLocalMover(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)
}
func testMoveLocalDirected(t *testing.T, test localDirectedMoveTest, g graph.Directed) {
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 := reduceDirected(reduceDirected(g, nil), communities)
l := newDirectedLocalMover(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)
}
}
}
@@ -471,105 +558,146 @@ func TestMoveLocalDirected(t *testing.T) {
}
func TestModularizeDirected(t *testing.T) {
const louvainIterations = 20
for _, test := range communityDirectedQTests {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
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))
testModularizeDirected(t, test, g)
}
}
func TestModularizeWeightedDirected(t *testing.T) {
for _, test := range communityDirectedQTests {
g := simple.NewWeightedDirectedGraph(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))
}
sort.Sort(ordered.ByID(want[i]))
}
sort.Sort(ordered.BySliceIDs(want))
var (
got *ReducedDirected
bestQ = math.Inf(-1)
)
// Modularize 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 := Modularize(g, 1, src).(*ReducedDirected)
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().(*ReducedDirected) {
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)
for v := range e {
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
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
}
testModularizeDirected(t, test, g)
}
}
func testModularizeDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
const louvainIterations = 20
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 *ReducedDirected
bestQ = math.Inf(-1)
)
// Modularize 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 := Modularize(g, 1, src).(*ReducedDirected)
if q := Q(r, nil, 1); q > bestQ || math.IsNaN(q) {
bestQ = q
got = r
var levels []level
for p := got; p != nil; p = p.Expanded().(*ReducedDirected) {
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 = reduceDirected(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)
// Don't try again for non-connected case.
break
}
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)
var qs []float64
for p := r; p != nil; p = p.Expanded().(*ReducedDirected) {
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)
return
}
var levels []level
for p := got; p != nil; p = p.Expanded().(*ReducedDirected) {
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 = reduceDirected(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)
}
}
func TestNonContiguousDirected(t *testing.T) {
g := simple.NewDirectedGraph(0, 0)
g := simple.NewDirectedGraph()
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(4), T: simple.Node(5)},
} {
g.SetEdge(e)
}
func() {
defer func() {
r := recover()
if r != nil {
t.Error("unexpected panic with non-contiguous ID range")
}
}()
Modularize(g, 1, nil)
}()
}
func TestNonContiguousWeightedDirected(t *testing.T) {
g := simple.NewWeightedDirectedGraph(0, 0)
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 1},
{F: simple.Node(4), T: simple.Node(5), W: 1},
} {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
func() {

View File

@@ -229,8 +229,8 @@ func hasNegative(f []float64) bool {
}
var (
dupGraph = simple.NewUndirectedGraph(0, 0)
dupGraphDirected = simple.NewDirectedGraph(0, 0)
dupGraph = simple.NewUndirectedGraph()
dupGraphDirected = simple.NewDirectedGraph()
)
func init() {

View File

@@ -616,12 +616,32 @@ func TestLouvainMultiplex(t *testing.T) {
}
func TestNonContiguousUndirectedMultiplex(t *testing.T) {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(4), T: simple.Node(5)},
} {
g.SetEdge(e)
}
func() {
defer func() {
r := recover()
if r != nil {
t.Error("unexpected panic with non-contiguous ID range")
}
}()
ModularizeMultiplex(UndirectedLayers{g}, nil, nil, true, nil)
}()
}
func TestNonContiguousWeightedUndirectedMultiplex(t *testing.T) {
g := simple.NewWeightedUndirectedGraph(0, 0)
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 1},
{F: simple.Node(4), T: simple.Node(5), W: 1},
} {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
func() {
@@ -646,7 +666,7 @@ func undirectedMultiplexFrom(raw []layer) (UndirectedLayers, []float64, error) {
var layers []graph.Undirected
var weights []float64
for _, l := range raw {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewWeightedUndirectedGraph(0, 0)
for u, e := range l.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -657,7 +677,7 @@ func undirectedMultiplexFrom(raw []layer) (UndirectedLayers, []float64, error) {
if l.edgeWeight != 0 {
w = l.edgeWeight
}
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: w})
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: w})
}
}
layers = append(layers, g)

View File

@@ -17,13 +17,15 @@ import (
"gonum.org/v1/gonum/graph/simple"
)
var communityUndirectedQTests = []struct {
type communityUndirectedQTest struct {
name string
g []intset
structures []structure
wantLevels []level
}{
}
var communityUndirectedQTests = []communityUndirectedQTest{
// The java reference implementation is available from http://www.ludowaltman.nl/slm/.
{
name: "unconnected",
@@ -258,194 +260,258 @@ var communityUndirectedQTests = []struct {
func TestCommunityQUndirected(t *testing.T) {
for _, test := range communityUndirectedQTests {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
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))
}
testCommunityQUndirected(t, test, g)
}
}
func TestCommunityQWeightedUndirected(t *testing.T) {
for _, test := range communityUndirectedQTests {
g := simple.NewWeightedUndirectedGraph(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))
}
got := Q(g, communities, structure.resolution)
if !floats.EqualWithinAbsOrRel(got, structure.want, structure.tol, structure.tol) && !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)
for v := range e {
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
testCommunityQUndirected(t, test, g)
}
}
func testCommunityQUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
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(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)
}
}
}
func TestCommunityDeltaQUndirected(t *testing.T) {
tests:
for _, test := range communityUndirectedQTests {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
rnd := rand.New(rand.NewSource(1)).Intn
for _, structure := range test.structures {
communityOf := make(map[int64]int)
communities := make([][]graph.Node, len(structure.memberships))
testCommunityDeltaQUndirected(t, test, g)
}
}
func TestCommunityDeltaQWeightedUndirected(t *testing.T) {
for _, test := range communityUndirectedQTests {
g := simple.NewWeightedUndirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
testCommunityDeltaQUndirected(t, test, g)
}
}
func testCommunityDeltaQUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
rnd := rand.New(rand.NewSource(1)).Intn
for _, structure := range test.structures {
communityOf := make(map[int64]int)
communities := make([][]graph.Node, len(structure.memberships))
for i, c := range structure.memberships {
for n := range c {
n := int64(n)
communityOf[n] = i
communities[i] = append(communities[i], simple.Node(n))
}
sort.Sort(ordered.ByID(communities[i]))
}
before := Q(g, communities, structure.resolution)
l := newUndirectedLocalMover(reduceUndirected(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)
}
return
}
// 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 {
n := int64(n)
communityOf[n] = i
communities[i] = append(communities[i], simple.Node(n))
}
sort.Sort(ordered.ByID(communities[i]))
}
before := Q(g, communities, structure.resolution)
l := newUndirectedLocalMover(reduceUndirected(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 {
n := int64(n)
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()] {
if n == 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
}
migrated[i] = append(migrated[i], simple.Node(n))
}
sort.Sort(ordered.ByID(migrated[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)
for i, c := range structure.memberships {
if i == communityOf[target.ID()] {
continue
}
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
}
connected := false
for n := range c {
if g.HasEdgeBetween(simple.Node(n), target) {
connected = true
break
}
t.Errorf("unexpected source node index: got: %d want: %d", gotSrc.node, wantNodeIdx)
}
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)
}
}
}
}
func TestReduceQConsistencyUndirected(t *testing.T) {
tests:
for _, test := range communityUndirectedQTests {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
for _, structure := range test.structures {
if math.IsNaN(structure.want) {
continue tests
}
testReduceQConsistencyUndirected(t, test, g)
}
}
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]))
func TestReduceQConsistencyWeightedUndirected(t *testing.T) {
for _, test := range communityUndirectedQTests {
g := simple.NewWeightedUndirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
gQ := Q(g, communities, structure.resolution)
gQnull := Q(g, nil, 1)
testReduceQConsistencyUndirected(t, test, g)
}
}
cg0 := reduceUndirected(g, nil)
cg0Qnull := Q(cg0, cg0.Structure(), 1)
if !floats.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
t.Errorf("disagreement 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 reduction: got: %v want :%v", cg0Q, gQ)
}
func testReduceQConsistencyUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
for _, structure := range test.structures {
if math.IsNaN(structure.want) {
return
}
cg1 := reduceUndirected(cg0, communities)
cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
if !floats.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ)
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 := reduceUndirected(g, nil)
cg0Qnull := Q(cg0, cg0.Structure(), 1)
if !floats.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
t.Errorf("disagreement 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 reduction: got: %v want :%v", cg0Q, gQ)
}
cg1 := reduceUndirected(cg0, communities)
cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
if !floats.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ)
}
}
}
var localUndirectedMoveTests = []struct {
type localUndirectedMoveTest struct {
name string
g []intset
structures []moveStructures
}{
}
var localUndirectedMoveTests = []localUndirectedMoveTest{
{
name: "blondel",
g: blondel,
@@ -490,39 +556,60 @@ var localUndirectedMoveTests = []struct {
func TestMoveLocalUndirected(t *testing.T) {
for _, test := range localUndirectedMoveTests {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
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]))
testMoveLocalUndirected(t, test, g)
}
}
func TestMoveLocalWeightedUndirected(t *testing.T) {
for _, test := range localUndirectedMoveTests {
g := simple.NewWeightedUndirectedGraph(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.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
r := reduceUndirected(reduceUndirected(g, nil), communities)
testMoveLocalUndirected(t, test, g)
}
}
l := newUndirectedLocalMover(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)
}
func testMoveLocalUndirected(t *testing.T, test localUndirectedMoveTest, g graph.Undirected) {
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 := reduceUndirected(reduceUndirected(g, nil), communities)
l := newUndirectedLocalMover(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)
}
}
}
@@ -530,105 +617,146 @@ func TestMoveLocalUndirected(t *testing.T) {
}
func TestModularizeUndirected(t *testing.T) {
const louvainIterations = 20
for _, test := range communityUndirectedQTests {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
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})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
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))
testModularizeUndirected(t, test, g)
}
}
func TestModularizeWeightedUndirected(t *testing.T) {
for _, test := range communityUndirectedQTests {
g := simple.NewWeightedUndirectedGraph(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))
}
sort.Sort(ordered.ByID(want[i]))
}
sort.Sort(ordered.BySliceIDs(want))
var (
got *ReducedUndirected
bestQ = math.Inf(-1)
)
// Modularize 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 := Modularize(g, 1, src).(*ReducedUndirected)
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().(*ReducedUndirected) {
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)
for v := range e {
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
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
}
testModularizeUndirected(t, test, g)
}
}
func testModularizeUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
const louvainIterations = 20
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)
)
// Modularize 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 := Modularize(g, 1, src).(*ReducedUndirected)
if q := Q(r, nil, 1); q > bestQ || math.IsNaN(q) {
bestQ = q
got = r
var levels []level
for p := got; p != nil; p = p.Expanded().(*ReducedUndirected) {
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 = reduceUndirected(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)
// Don't try again for non-connected case.
break
}
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)
var qs []float64
for p := r; p != nil; p = p.Expanded().(*ReducedUndirected) {
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)
return
}
var levels []level
for p := got; p != nil; p = p.Expanded().(*ReducedUndirected) {
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 = reduceUndirected(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)
}
}
func TestNonContiguousUndirected(t *testing.T) {
g := simple.NewUndirectedGraph(0, 0)
g := simple.NewUndirectedGraph()
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(4), T: simple.Node(5)},
} {
g.SetEdge(e)
}
func() {
defer func() {
r := recover()
if r != nil {
t.Error("unexpected panic with non-contiguous ID range")
}
}()
Modularize(g, 1, nil)
}()
}
func TestNonContiguousWeightedUndirected(t *testing.T) {
g := simple.NewWeightedUndirectedGraph(0, 0)
for _, e := range []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 1},
{F: simple.Node(4), T: simple.Node(5), W: 1},
} {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
func() {

View File

@@ -110,7 +110,7 @@ type dotDirectedGraph struct {
// newDotDirectedGraph returns a new directed capable of creating user-defined
// nodes and edges.
func newDotDirectedGraph() *dotDirectedGraph {
return &dotDirectedGraph{DirectedGraph: simple.NewDirectedGraph(0, 0)}
return &dotDirectedGraph{DirectedGraph: simple.NewDirectedGraph()}
}
// NewNode returns a new node with a unique node ID for the graph.
@@ -145,7 +145,7 @@ type dotUndirectedGraph struct {
// newDotUndirectedGraph returns a new undirected capable of creating user-
// defined nodes and edges.
func newDotUndirectedGraph() *dotUndirectedGraph {
return &dotUndirectedGraph{UndirectedGraph: simple.NewUndirectedGraph(0, 0)}
return &dotUndirectedGraph{UndirectedGraph: simple.NewUndirectedGraph()}
}
// NewNode adds a new node with a unique node ID to the graph.

View File

@@ -5,7 +5,6 @@
package dot
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
@@ -55,7 +54,7 @@ var (
)
func directedGraphFrom(g []intset) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1))
dg := simple.NewDirectedGraph()
for u, e := range g {
for v := range e {
dg.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
@@ -65,7 +64,7 @@ func directedGraphFrom(g []intset) graph.Directed {
}
func undirectedGraphFrom(g []intset) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1))
dg := simple.NewUndirectedGraph()
for u, e := range g {
for v := range e {
dg.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
@@ -85,7 +84,7 @@ func (n namedNode) ID() int64 { return n.id }
func (n namedNode) DOTID() string { return n.name }
func directedNamedIDGraphFrom(g []intset) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1))
dg := simple.NewDirectedGraph()
for u, e := range g {
u := int64(u)
nu := namedNode{id: u, name: alpha[u : u+1]}
@@ -98,7 +97,7 @@ func directedNamedIDGraphFrom(g []intset) graph.Directed {
}
func undirectedNamedIDGraphFrom(g []intset) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1))
dg := simple.NewUndirectedGraph()
for u, e := range g {
u := int64(u)
nu := namedNode{id: u, name: alpha[u : u+1]}
@@ -120,7 +119,7 @@ func (n attrNode) ID() int64 { return n.id }
func (n attrNode) Attributes() []encoding.Attribute { return n.attr }
func directedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1))
dg := simple.NewDirectedGraph()
for u, e := range g {
u := int64(u)
var at []encoding.Attribute
@@ -140,7 +139,7 @@ func directedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Di
}
func undirectedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1))
dg := simple.NewUndirectedGraph()
for u, e := range g {
u := int64(u)
var at []encoding.Attribute
@@ -170,7 +169,7 @@ func (n namedAttrNode) DOTID() string { return n.name }
func (n namedAttrNode) Attributes() []encoding.Attribute { return n.attr }
func directedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1))
dg := simple.NewDirectedGraph()
for u, e := range g {
u := int64(u)
var at []encoding.Attribute
@@ -190,7 +189,7 @@ func directedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) g
}
func undirectedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1))
dg := simple.NewUndirectedGraph()
for u, e := range g {
u := int64(u)
var at []encoding.Attribute
@@ -221,7 +220,7 @@ func (e attrEdge) Weight() float64 { return 0 }
func (e attrEdge) Attributes() []encoding.Attribute { return e.attr }
func directedEdgeAttrGraphFrom(g []intset, attr map[edge][]encoding.Attribute) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1))
dg := simple.NewDirectedGraph()
for u, e := range g {
u := int64(u)
for v := range e {
@@ -232,7 +231,7 @@ func directedEdgeAttrGraphFrom(g []intset, attr map[edge][]encoding.Attribute) g
}
func undirectedEdgeAttrGraphFrom(g []intset, attr map[edge][]encoding.Attribute) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1))
dg := simple.NewUndirectedGraph()
for u, e := range g {
u := int64(u)
for v := range e {
@@ -273,7 +272,7 @@ func (e portedEdge) ToPort() (port, compass string) {
}
func directedPortedAttrGraphFrom(g []intset, attr [][]encoding.Attribute, ports map[edge]portedEdge) graph.Directed {
dg := simple.NewDirectedGraph(0, math.Inf(1))
dg := simple.NewDirectedGraph()
for u, e := range g {
u := int64(u)
var at []encoding.Attribute
@@ -295,7 +294,7 @@ func directedPortedAttrGraphFrom(g []intset, attr [][]encoding.Attribute, ports
}
func undirectedPortedAttrGraphFrom(g []intset, attr [][]encoding.Attribute, ports map[edge]portedEdge) graph.Graph {
dg := simple.NewUndirectedGraph(0, math.Inf(1))
dg := simple.NewUndirectedGraph()
for u, e := range g {
u := int64(u)
var at []encoding.Attribute
@@ -337,10 +336,10 @@ type structuredGraph struct {
}
func undirectedStructuredGraphFrom(c []edge, g ...[]intset) graph.Graph {
s := &structuredGraph{UndirectedGraph: simple.NewUndirectedGraph(0, math.Inf(1))}
s := &structuredGraph{UndirectedGraph: simple.NewUndirectedGraph()}
var base int64
for i, sg := range g {
sub := simple.NewUndirectedGraph(0, math.Inf(1))
sub := simple.NewUndirectedGraph()
for u, e := range sg {
u := int64(u)
for v := range e {
@@ -382,7 +381,7 @@ func undirectedSubGraphFrom(g []intset, s map[int64][]intset) graph.Graph {
var base int64
subs := make(map[int64]subGraph)
for i, sg := range s {
sub := simple.NewUndirectedGraph(0, math.Inf(1))
sub := simple.NewUndirectedGraph()
for u, e := range sg {
u := int64(u)
for v := range e {
@@ -394,7 +393,7 @@ func undirectedSubGraphFrom(g []intset, s map[int64][]intset) graph.Graph {
base += int64(len(sg))
}
dg := simple.NewUndirectedGraph(0, math.Inf(1))
dg := simple.NewUndirectedGraph()
for u, e := range g {
u := int64(u)
var nu graph.Node

View File

@@ -155,7 +155,7 @@ type directedGraph struct {
}
func newDirectedGraph() *directedGraph {
return &directedGraph{DirectedGraph: simple.NewDirectedGraph(0, 0)}
return &directedGraph{DirectedGraph: simple.NewDirectedGraph()}
}
func (g *directedGraph) NewNode() graph.Node {

View File

@@ -15,13 +15,15 @@ type Node interface {
type Edge interface {
From() Node
To() Node
Weight() float64
}
// WeightedEdge is a graph edge. In directed graphs, the direction
// WeightedEdge is a weighted graph edge. In directed graphs, the direction
// of the edge is given from -> to, otherwise the edge is semantically
// unordered.
type WeightedEdge Edge
type WeightedEdge interface {
Edge
Weight() float64
}
// Graph is a generalized graph.
type Graph interface {
@@ -143,6 +145,22 @@ type EdgeAdder interface {
SetEdge(e Edge)
}
// WeightedEdgeAdder is an interface for adding edges to a graph.
type WeightedEdgeAdder interface {
// NewWeightedEdge returns a new WeightedEdge from
// the source to the destination node.
NewWeightedEdge(from, to Node, weight float64) WeightedEdge
// SetWeightedEdge adds an edge from one node to
// another. If the graph supports node addition
// the nodes will be added if they do not exist,
// otherwise SetWeightedEdge will panic.
// The behavior of a WeightedEdgeAdder when the IDs
// returned by e.From and e.To are equal is
// implementation-dependent.
SetWeightedEdge(e WeightedEdge)
}
// EdgeRemover is an interface for removing nodes from a graph.
type EdgeRemover interface {
// RemoveEdge removes the given edge, leaving the
@@ -157,29 +175,42 @@ type Builder interface {
EdgeAdder
}
// WeightedBuilder is a graph that can have nodes and weighted edges added.
type WeightedBuilder interface {
NodeAdder
WeightedEdgeAdder
}
// UndirectedBuilder is an undirected graph builder.
type UndirectedBuilder interface {
Undirected
Builder
}
// UndirectedWeightedBuilder is an undirected weighted graph builder.
type UndirectedWeightedBuilder interface {
Undirected
WeightedBuilder
}
// DirectedBuilder is a directed graph builder.
type DirectedBuilder interface {
Directed
Builder
}
// DirectedWeightedBuilder is a directed weighted graph builder.
type DirectedWeightedBuilder interface {
Directed
WeightedBuilder
}
// Copy copies nodes and edges as undirected edges from the source to the destination
// without first clearing the destination. Copy will panic if a node ID in the source
// graph matches a node ID in the destination.
//
// If the source is undirected and the destination is directed both directions will
// be present in the destination after the copy is complete.
//
// If the source is a directed graph, the destination is undirected, and a fundamental
// cycle exists with two nodes where the edge weights differ, the resulting destination
// graph's edge weight between those nodes is undefined. If there is a defined function
// to resolve such conflicts, an Undirect may be used to do this.
func Copy(dst Builder, src Graph) {
nodes := src.Nodes()
for _, n := range nodes {
@@ -191,3 +222,26 @@ func Copy(dst Builder, src Graph) {
}
}
}
// CopyWeighted copies nodes and edges as undirected edges from the source to the destination
// without first clearing the destination. Copy will panic if a node ID in the source
// graph matches a node ID in the destination.
//
// If the source is undirected and the destination is directed both directions will
// be present in the destination after the copy is complete.
//
// If the source is a directed graph, the destination is undirected, and a fundamental
// cycle exists with two nodes where the edge weights differ, the resulting destination
// graph's edge weight between those nodes is undefined. If there is a defined function
// to resolve such conflicts, an UndirectWeighted may be used to do this.
func CopyWeighted(dst WeightedBuilder, src Weighted) {
nodes := src.Nodes()
for _, n := range nodes {
dst.AddNode(n)
}
for _, u := range nodes {
for _, v := range src.From(u) {
dst.SetWeightedEdge(src.WeightedEdge(u, v))
}
}
}

View File

@@ -5,7 +5,6 @@
package gen
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
@@ -54,7 +53,7 @@ func (g *gnDirected) SetEdge(e graph.Edge) {
func TestGnpUndirected(t *testing.T) {
for n := 2; n <= 20; n++ {
for p := 0.; p <= 1; p += 0.1 {
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))}
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()}
err := Gnp(g, n, p, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err)
@@ -75,7 +74,7 @@ func TestGnpUndirected(t *testing.T) {
func TestGnpDirected(t *testing.T) {
for n := 2; n <= 20; n++ {
for p := 0.; p <= 1; p += 0.1 {
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))}
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph()}
err := Gnp(g, n, p, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err)
@@ -94,7 +93,7 @@ func TestGnmUndirected(t *testing.T) {
for n := 2; n <= 20; n++ {
nChoose2 := (n - 1) * n / 2
for m := 0; m <= nChoose2; m++ {
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))}
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()}
err := Gnm(g, n, m, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err)
@@ -116,7 +115,7 @@ func TestGnmDirected(t *testing.T) {
for n := 2; n <= 20; n++ {
nChoose2 := (n - 1) * n / 2
for m := 0; m <= nChoose2*2; m++ {
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))}
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph()}
err := Gnm(g, n, m, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err)
@@ -135,7 +134,7 @@ func TestSmallWorldsBBUndirected(t *testing.T) {
for n := 2; n <= 20; n++ {
for d := 1; d <= (n-1)/2; d++ {
for p := 0.; p < 1; p += 0.1 {
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))}
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()}
err := SmallWorldsBB(g, n, d, p, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err)
@@ -158,7 +157,7 @@ func TestSmallWorldsBBDirected(t *testing.T) {
for n := 2; n <= 20; n++ {
for d := 1; d <= (n-1)/2; d++ {
for p := 0.; p < 1; p += 0.1 {
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))}
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph()}
err := SmallWorldsBB(g, n, d, p, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err)

View File

@@ -5,7 +5,6 @@
package gen
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
@@ -38,7 +37,7 @@ func TestDuplication(t *testing.T) {
for alpha := 0.1; alpha <= 1; alpha += 0.1 {
for delta := 0.; delta <= 1; delta += 0.2 {
for sigma := 0.; sigma <= 1; sigma += 0.2 {
g := &duplication{UndirectedMutator: simple.NewUndirectedGraph(0, math.Inf(1))}
g := &duplication{UndirectedMutator: simple.NewUndirectedGraph()}
err := Duplication(g, n, delta, alpha, sigma, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, alpha=%v, delta=%v sigma=%v: %v", n, alpha, delta, sigma, err)

View File

@@ -5,7 +5,6 @@
package gen
import (
"math"
"testing"
"gonum.org/v1/gonum/graph/simple"
@@ -15,7 +14,7 @@ func TestTunableClusteringScaleFree(t *testing.T) {
for n := 2; n <= 20; n++ {
for m := 0; m < n; m++ {
for p := 0.; p <= 1; p += 0.1 {
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))}
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()}
err := TunableClusteringScaleFree(g, n, m, p, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, m=%d, p=%v: %v", n, m, p, err)
@@ -37,7 +36,7 @@ func TestTunableClusteringScaleFree(t *testing.T) {
func TestPreferentialAttachment(t *testing.T) {
for n := 2; n <= 20; n++ {
for m := 0; m < n; m++ {
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))}
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()}
err := PreferentialAttachment(g, n, m, nil)
if err != nil {
t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err)

View File

@@ -5,7 +5,6 @@
package gen
import (
"math"
"testing"
"gonum.org/v1/gonum/graph/simple"
@@ -22,7 +21,7 @@ func TestNavigableSmallWorldUndirected(t *testing.T) {
for q := 0; q < 10; q++ {
for r := 0.5; r < 10; r++ {
for _, dims := range smallWorldDimensionParameters {
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))}
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()}
err := NavigableSmallWorld(g, dims, p, q, r, nil)
n := 1
for _, d := range dims {
@@ -51,7 +50,7 @@ func TestNavigableSmallWorldDirected(t *testing.T) {
for q := 0; q < 10; q++ {
for r := 0.5; r < 10; r++ {
for _, dims := range smallWorldDimensionParameters {
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))}
g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph()}
err := NavigableSmallWorld(g, dims, p, q, r, nil)
n := 1
for _, d := range dims {

View File

@@ -176,15 +176,14 @@ var betweennessTests = []struct {
func TestBetweenness(t *testing.T) {
for i, test := range betweennessTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
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 {
// Weight omitted to show weight-independence.
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 0})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
got := Betweenness(g)
@@ -206,15 +205,14 @@ func TestBetweenness(t *testing.T) {
func TestEdgeBetweenness(t *testing.T) {
for i, test := range betweennessTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
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 {
// Weight omitted to show weight-independence.
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 0})
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
got := EdgeBetweenness(g)
@@ -239,14 +237,14 @@ func TestEdgeBetweenness(t *testing.T) {
func TestBetweennessWeighted(t *testing.T) {
for i, test := range betweennessTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
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})
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
@@ -275,14 +273,14 @@ func TestBetweennessWeighted(t *testing.T) {
func TestEdgeBetweennessWeighted(t *testing.T) {
for i, test := range betweennessTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
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})
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}

View File

@@ -143,14 +143,14 @@ func TestDistanceCentralityUndirected(t *testing.T) {
prec := 1 - int(math.Log10(tol))
for i, test := range undirectedCentralityTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
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})
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
p, ok := path.FloydWarshall(g)
@@ -333,14 +333,14 @@ func TestDistanceCentralityDirected(t *testing.T) {
prec := 1 - int(math.Log10(tol))
for i, test := range directedCentralityTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewWeightedDirectedGraph(0, math.Inf(1))
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})
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
}
}
p, ok := path.FloydWarshall(g)

View File

@@ -43,7 +43,7 @@ var hitsTests = []struct {
func TestHITS(t *testing.T) {
for i, test := range hitsTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {

View File

@@ -81,7 +81,7 @@ var pageRankTests = []struct {
func TestPageRank(t *testing.T) {
for i, test := range pageRankTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -105,7 +105,7 @@ func TestPageRank(t *testing.T) {
func TestPageRankSparse(t *testing.T) {
for i, test := range pageRankTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {

View File

@@ -154,7 +154,7 @@ func TestAStar(t *testing.T) {
}
func TestExhaustiveAStar(t *testing.T) {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
nodes := []locatedNode{
{id: 1, x: 0, y: 6},
{id: 2, x: 1, y: 0},
@@ -179,7 +179,7 @@ func TestExhaustiveAStar(t *testing.T) {
{from: g.Node(5), to: g.Node(6), cost: 9},
}
for _, e := range edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
heuristic := func(u, v graph.Node) float64 {
@@ -247,7 +247,7 @@ func TestAStarNullHeuristic(t *testing.T) {
for _, test := range testgraphs.ShortestPathTests {
g := test.Graph()
for _, e := range test.Edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
var (

View File

@@ -17,7 +17,7 @@ func TestBellmanFordFrom(t *testing.T) {
for _, test := range testgraphs.ShortestPathTests {
g := test.Graph()
for _, e := range test.Edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
pt, ok := BellmanFordFrom(test.Query.From(), g.(graph.Graph))

View File

@@ -5,7 +5,6 @@
package path
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
@@ -23,7 +22,7 @@ var (
)
func gnpUndirected(n int, p float64) graph.Undirected {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
gen.Gnp(g, n, p, nil)
return g
}
@@ -65,7 +64,7 @@ var (
)
func navigableSmallWorldUndirected(n, p, q int, r float64) graph.Undirected {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
gen.NavigableSmallWorld(g, []int{n, n}, p, q, r, nil)
return g
}

View File

@@ -19,7 +19,7 @@ func TestDijkstraFrom(t *testing.T) {
for _, test := range testgraphs.ShortestPathTests {
g := test.Graph()
for _, e := range test.Edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
var (
@@ -85,7 +85,7 @@ func TestDijkstraAllPaths(t *testing.T) {
for _, test := range testgraphs.ShortestPathTests {
g := test.Graph()
for _, e := range test.Edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
var (

View File

@@ -33,7 +33,7 @@ type DStarLite struct {
// WorldModel is a mutable weighted directed graph that returns nodes identified
// by id number.
type WorldModel interface {
graph.Builder
graph.WeightedBuilder
graph.WeightedDirected
Node(id int64) graph.Node
}
@@ -104,7 +104,7 @@ func NewDStarLite(s, t graph.Node, g graph.Graph, h path.Heuristic, m WorldModel
if w < 0 {
panic("D* Lite: negative edge weight")
}
d.model.SetEdge(simple.Edge{F: u, T: d.model.Node(v.ID()), W: w})
d.model.SetWeightedEdge(simple.Edge{F: u, T: d.model.Node(v.ID()), W: w})
}
}
@@ -302,7 +302,7 @@ func (d *DStarLite) UpdateWorld(changes []graph.Edge) {
cOld, _ := d.model.Weight(from, to)
u := d.worldNodeFor(from)
v := d.worldNodeFor(to)
d.model.SetEdge(simple.Edge{F: u, T: v, W: c})
d.model.SetWeightedEdge(simple.Edge{F: u, T: v, W: c})
if cOld > c {
if u.ID() != d.t.ID() {
u.rhs = math.Min(u.rhs, c+v.g)

View File

@@ -35,7 +35,7 @@ func TestDStarLiteNullHeuristic(t *testing.T) {
g := test.Graph()
for _, e := range test.Edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
var (
@@ -47,7 +47,7 @@ func TestDStarLiteNullHeuristic(t *testing.T) {
defer func() {
panicked = recover() != nil
}()
d = NewDStarLite(test.Query.From(), test.Query.To(), g.(graph.Graph), path.NullHeuristic, simple.NewDirectedGraph(0, math.Inf(1)))
d = NewDStarLite(test.Query.From(), test.Query.To(), g.(graph.Graph), path.NullHeuristic, simple.NewWeightedDirectedGraph(0, math.Inf(1)))
}()
if panicked || test.HasNegativeWeight {
if !test.HasNegativeWeight {
@@ -579,7 +579,7 @@ func TestDStarLiteDynamic(t *testing.T) {
return test.heuristic(ax-bx, ay-by)
}
world := simple.NewDirectedGraph(0, math.Inf(1))
world := simple.NewWeightedDirectedGraph(0, math.Inf(1))
d := NewDStarLite(test.s, test.t, l, heuristic, world)
var (
dp *dumper

View File

@@ -19,7 +19,7 @@ func TestFloydWarshall(t *testing.T) {
for _, test := range testgraphs.ShortestPathTests {
g := test.Graph()
for _, e := range test.Edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
pt, ok := FloydWarshall(g.(graph.Graph))

View File

@@ -25,7 +25,7 @@ func init() {
// dynamic shortest path routine in path/dynamic: DStarLite.
var ShortestPathTests = []struct {
Name string
Graph func() graph.EdgeAdder
Graph func() graph.WeightedEdgeAdder
Edges []simple.Edge
HasNegativeWeight bool
HasNegativeCycle bool
@@ -40,7 +40,7 @@ var ShortestPathTests = []struct {
// Positive weighted graphs.
{
Name: "empty directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
Weight: math.Inf(1),
@@ -49,7 +49,7 @@ var ShortestPathTests = []struct {
},
{
Name: "empty undirected",
Graph: func() graph.EdgeAdder { return simple.NewUndirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
Weight: math.Inf(1),
@@ -58,7 +58,7 @@ var ShortestPathTests = []struct {
},
{
Name: "one edge directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 1},
},
@@ -74,7 +74,7 @@ var ShortestPathTests = []struct {
},
{
Name: "one edge self directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 1},
},
@@ -90,7 +90,7 @@ var ShortestPathTests = []struct {
},
{
Name: "one edge undirected",
Graph: func() graph.EdgeAdder { return simple.NewUndirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 1},
},
@@ -106,7 +106,7 @@ var ShortestPathTests = []struct {
},
{
Name: "two paths directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(2), W: 2},
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -125,7 +125,7 @@ var ShortestPathTests = []struct {
},
{
Name: "two paths undirected",
Graph: func() graph.EdgeAdder { return simple.NewUndirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(2), W: 2},
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -144,7 +144,7 @@ var ShortestPathTests = []struct {
},
{
Name: "confounding paths directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->5 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -178,7 +178,7 @@ var ShortestPathTests = []struct {
},
{
Name: "confounding paths undirected",
Graph: func() graph.EdgeAdder { return simple.NewUndirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->5 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -212,7 +212,7 @@ var ShortestPathTests = []struct {
},
{
Name: "confounding paths directed 2-step",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->5 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -247,7 +247,7 @@ var ShortestPathTests = []struct {
},
{
Name: "confounding paths undirected 2-step",
Graph: func() graph.EdgeAdder { return simple.NewUndirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->5 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -282,7 +282,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight cycle directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->4 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -306,7 +306,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight cycle^2 directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->4 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -333,7 +333,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight cycle^2 confounding directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->4 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -363,7 +363,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight cycle^3 directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->4 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -393,7 +393,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight 3·cycle^2 confounding directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->4 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -429,7 +429,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight reversed 3·cycle^2 confounding directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
// Add a path from 0->4 of weight 4
{F: simple.Node(0), T: simple.Node(1), W: 1},
@@ -465,7 +465,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight |V|·cycle^(n/|V|) directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: func() []simple.Edge {
e := []simple.Edge{
// Add a path from 0->4 of weight 4
@@ -498,7 +498,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight n·cycle directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: func() []simple.Edge {
e := []simple.Edge{
// Add a path from 0->4 of weight 4
@@ -531,7 +531,7 @@ var ShortestPathTests = []struct {
},
{
Name: "zero-weight bi-directional tree with single exit directed",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: func() []simple.Edge {
e := []simple.Edge{
// Add a path from 0->4 of weight 4
@@ -579,7 +579,7 @@ var ShortestPathTests = []struct {
// Negative weighted graphs.
{
Name: "one edge directed negative",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: -1},
},
@@ -596,7 +596,7 @@ var ShortestPathTests = []struct {
},
{
Name: "one edge undirected negative",
Graph: func() graph.EdgeAdder { return simple.NewUndirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: -1},
},
@@ -607,7 +607,7 @@ var ShortestPathTests = []struct {
},
{
Name: "wp graph negative", // http://en.wikipedia.org/w/index.php?title=Johnson%27s_algorithm&oldid=564595231
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node('w'), T: simple.Node('z'), W: 2},
{F: simple.Node('x'), T: simple.Node('w'), W: 6},
@@ -630,7 +630,7 @@ var ShortestPathTests = []struct {
},
{
Name: "roughgarden negative",
Graph: func() graph.EdgeAdder { return simple.NewDirectedGraph(0, math.Inf(1)) },
Graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
Edges: []simple.Edge{
{F: simple.Node('a'), T: simple.Node('b'), W: -2},
{F: simple.Node('b'), T: simple.Node('c'), W: -1},

View File

@@ -19,7 +19,7 @@ func TestJohnsonAllPaths(t *testing.T) {
for _, test := range testgraphs.ShortestPathTests {
g := test.Graph()
for _, e := range test.Edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
pt, ok := JohnsonAllPaths(g.(graph.Graph))

View File

@@ -26,7 +26,7 @@ func init() {
}
type spanningGraph interface {
graph.Builder
graph.WeightedBuilder
graph.WeightedUndirected
Edges() []graph.Edge
}
@@ -40,7 +40,7 @@ var spanningTreeTests = []struct {
}{
{
name: "Empty",
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
want: 0,
},
{
@@ -48,7 +48,7 @@ var spanningTreeTests = []struct {
// Modified to make edge weights unique; A--B is increased to 2.5 otherwise
// to prevent the alternative solution being found.
name: "Prim WP figure 1",
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
edges: []simple.Edge{
{F: simple.Node('A'), T: simple.Node('B'), W: 2.5},
{F: simple.Node('A'), T: simple.Node('D'), W: 1},
@@ -66,7 +66,7 @@ var spanningTreeTests = []struct {
{
// https://upload.wikimedia.org/wikipedia/commons/5/5c/MST_kruskal_en.gif
name: "Kruskal WP figure 1",
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
edges: []simple.Edge{
{F: simple.Node('a'), T: simple.Node('b'), W: 3},
{F: simple.Node('a'), T: simple.Node('e'), W: 1},
@@ -88,7 +88,7 @@ var spanningTreeTests = []struct {
{
// https://upload.wikimedia.org/wikipedia/commons/8/87/Kruskal_Algorithm_6.svg
name: "Kruskal WP example",
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
edges: []simple.Edge{
{F: simple.Node('A'), T: simple.Node('B'), W: 7},
{F: simple.Node('A'), T: simple.Node('D'), W: 5},
@@ -116,7 +116,7 @@ var spanningTreeTests = []struct {
{
// https://upload.wikimedia.org/wikipedia/commons/2/2e/Boruvka%27s_algorithm_%28Sollin%27s_algorithm%29_Anim.gif
name: "Borůvka WP example",
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
edges: []simple.Edge{
{F: simple.Node('A'), T: simple.Node('B'), W: 13},
{F: simple.Node('A'), T: simple.Node('C'), W: 6},
@@ -159,7 +159,7 @@ var spanningTreeTests = []struct {
// https://upload.wikimedia.org/wikipedia/commons/d/d2/Minimum_spanning_tree.svg
// Nodes labelled row major.
name: "Minimum Spanning Tree WP figure 1",
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
edges: []simple.Edge{
{F: simple.Node(1), T: simple.Node(2), W: 4},
{F: simple.Node(1), T: simple.Node(3), W: 1},
@@ -202,7 +202,7 @@ var spanningTreeTests = []struct {
// https://upload.wikimedia.org/wikipedia/commons/2/2e/Boruvka%27s_algorithm_%28Sollin%27s_algorithm%29_Anim.gif
// but with C--H and E--J cut.
name: "Borůvka WP example cut",
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
edges: []simple.Edge{
{F: simple.Node('A'), T: simple.Node('B'), W: 13},
{F: simple.Node('A'), T: simple.Node('C'), W: 6},
@@ -244,17 +244,17 @@ func testMinumumSpanning(mst func(dst graph.UndirectedBuilder, g spanningGraph)
for _, test := range spanningTreeTests {
g := test.graph()
for _, e := range test.edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
dst := simple.NewUndirectedGraph(0, math.Inf(1))
dst := edgeAdder{simple.NewWeightedUndirectedGraph(0, math.Inf(1))}
w := mst(dst, g)
if w != test.want {
t.Errorf("unexpected minimum spanning tree weight for %q: got: %f want: %f",
test.name, w, test.want)
}
var got float64
for _, e := range dst.Edges() {
for _, e := range dst.WeightedEdges() {
got += e.Weight()
}
if got != test.want {
@@ -281,6 +281,18 @@ func testMinumumSpanning(mst func(dst graph.UndirectedBuilder, g spanningGraph)
}
}
type edgeAdder struct {
*simple.WeightedUndirectedGraph
}
func (g edgeAdder) NewEdge(x, y graph.Node) graph.Edge {
return g.WeightedUndirectedGraph.NewWeightedEdge(x, y, 1)
}
func (g edgeAdder) SetEdge(e graph.Edge) {
g.WeightedUndirectedGraph.SetWeightedEdge(e.(graph.WeightedEdge))
}
func TestKruskal(t *testing.T) {
testMinumumSpanning(func(dst graph.UndirectedBuilder, g spanningGraph) float64 {
return Kruskal(dst, g)

View File

@@ -218,9 +218,19 @@ func (g *DirectedMatrix) Weight(x, y graph.Node) (w float64, ok bool) {
return g.absent, false
}
// SetEdge sets e, an edge from one node to another. If the ends of the edge are not in g
// or the edge is a self loop, SetEdge panics.
// SetEdge sets e, an edge from one node to another with unit weight. If the ends of the edge
// are not in g or the edge is a self loop, SetEdge panics.
func (g *DirectedMatrix) SetEdge(e graph.Edge) {
g.setWeightedEdge(e, 1)
}
// SetWeightedEdge sets e, an edge from one node to another. If the ends of the edge are not in g
// or the edge is a self loop, SetWeightedEdge panics.
func (g *DirectedMatrix) SetWeightedEdge(e graph.WeightedEdge) {
g.setWeightedEdge(e, e.Weight())
}
func (g *DirectedMatrix) setWeightedEdge(e graph.Edge, weight float64) {
fid := e.From().ID()
tid := e.To().ID()
if fid == tid {
@@ -233,7 +243,7 @@ func (g *DirectedMatrix) SetEdge(e graph.Edge) {
panic("simple: unavailable to node ID for dense graph")
}
// fid and tid are not greater than maximum int by this point.
g.mat.Set(int(fid), int(tid), e.Weight())
g.mat.Set(int(fid), int(tid), weight)
}
// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist

View File

@@ -190,9 +190,19 @@ func (g *UndirectedMatrix) Weight(x, y graph.Node) (w float64, ok bool) {
return g.absent, false
}
// SetEdge sets e, an edge from one node to another. If the ends of the edge are not in g
// or the edge is a self loop, SetEdge panics.
// SetEdge sets e, an edge from one node to another with unit weight. If the ends of the edge are
// not in g or the edge is a self loop, SetEdge panics.
func (g *UndirectedMatrix) SetEdge(e graph.Edge) {
g.setWeightedEdge(e, 1)
}
// SetWeightedEdge sets e, an edge from one node to another. If the ends of the edge are not in g
// or the edge is a self loop, SetWeightedEdge panics.
func (g *UndirectedMatrix) SetWeightedEdge(e graph.WeightedEdge) {
g.setWeightedEdge(e, e.Weight())
}
func (g *UndirectedMatrix) setWeightedEdge(e graph.Edge, weight float64) {
fid := e.From().ID()
tid := e.To().ID()
if fid == tid {
@@ -205,7 +215,7 @@ func (g *UndirectedMatrix) SetEdge(e graph.Edge) {
panic("simple: unavailable to node ID for dense graph")
}
// fid and tid are not greater than maximum int by this point.
g.mat.SetSym(int(fid), int(tid), e.Weight())
g.mat.SetSym(int(fid), int(tid), weight)
}
// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist

View File

@@ -16,22 +16,17 @@ type DirectedGraph struct {
from map[int64]map[int64]graph.Edge
to map[int64]map[int64]graph.Edge
self, absent float64
nodeIDs idSet
}
// NewDirectedGraph returns a DirectedGraph with the specified self and absent
// edge weight values.
func NewDirectedGraph(self, absent float64) *DirectedGraph {
func NewDirectedGraph() *DirectedGraph {
return &DirectedGraph{
nodes: make(map[int64]graph.Node),
from: make(map[int64]map[int64]graph.Edge),
to: make(map[int64]map[int64]graph.Edge),
self: self,
absent: absent,
nodeIDs: newIDSet(),
}
}
@@ -213,12 +208,6 @@ func (g *DirectedGraph) HasEdgeBetween(x, y graph.Node) bool {
// Edge returns the edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *DirectedGraph) Edge(u, v graph.Node) graph.Edge {
return g.WeightedEdge(u, v)
}
// WeightedEdge returns the weighted edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *DirectedGraph) WeightedEdge(u, v graph.Node) graph.WeightedEdge {
if _, ok := g.nodes[u.ID()]; !ok {
return nil
}
@@ -246,24 +235,6 @@ func (g *DirectedGraph) HasEdgeFromTo(u, v graph.Node) bool {
return true
}
// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge.
// If x and y are the same node or there is no joining edge between the two nodes the weight
// value returned is either the graph's absent or self value. Weight returns true if an edge
// exists between x and y or if x and y have the same ID, false otherwise.
func (g *DirectedGraph) Weight(x, y graph.Node) (w float64, ok bool) {
xid := x.ID()
yid := y.ID()
if xid == yid {
return g.self, true
}
if to, ok := g.from[xid]; ok {
if e, ok := to[yid]; ok {
return e.Weight(), true
}
}
return g.absent, false
}
// Degree returns the in+out degree of n in g.
func (g *DirectedGraph) Degree(n graph.Node) int {
if _, ok := g.nodes[n.ID()]; !ok {

View File

@@ -5,7 +5,6 @@
package simple
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
@@ -14,9 +13,8 @@ import (
var (
directedGraph = (*DirectedGraph)(nil)
_ graph.Graph = directedGraph
_ graph.Directed = directedGraph
_ graph.WeightedDirected = directedGraph
_ graph.Graph = directedGraph
_ graph.Directed = directedGraph
)
// Tests Issue #27
@@ -36,10 +34,10 @@ func generateDummyGraph() *DirectedGraph {
{0, 2},
}
g := NewDirectedGraph(0, math.Inf(1))
g := NewDirectedGraph()
for _, n := range nodes {
g.SetEdge(Edge{F: Node(n.srcID), T: Node(n.targetID), W: 1})
g.SetEdge(Edge{F: Node(n.srcID), T: Node(n.targetID)})
}
return g
@@ -52,7 +50,7 @@ func TestIssue123DirectedGraph(t *testing.T) {
t.Errorf("unexpected panic: %v", r)
}
}()
g := NewDirectedGraph(0, math.Inf(1))
g := NewDirectedGraph()
n0 := g.NewNode()
g.AddNode(n0)

View File

@@ -15,21 +15,16 @@ type UndirectedGraph struct {
nodes map[int64]graph.Node
edges map[int64]map[int64]graph.Edge
self, absent float64
nodeIDs idSet
}
// NewUndirectedGraph returns an UndirectedGraph with the specified self and absent
// edge weight values.
func NewUndirectedGraph(self, absent float64) *UndirectedGraph {
func NewUndirectedGraph() *UndirectedGraph {
return &UndirectedGraph{
nodes: make(map[int64]graph.Node),
edges: make(map[int64]map[int64]graph.Edge),
self: self,
absent: absent,
nodeIDs: newIDSet(),
}
}
@@ -186,22 +181,11 @@ func (g *UndirectedGraph) HasEdgeBetween(x, y graph.Node) bool {
// Edge returns the edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *UndirectedGraph) Edge(u, v graph.Node) graph.Edge {
return g.WeightedEdgeBetween(u, v)
}
// WeightedEdge returns the weighted edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *UndirectedGraph) WeightedEdge(u, v graph.Node) graph.WeightedEdge {
return g.WeightedEdgeBetween(u, v)
return g.EdgeBetween(u, v)
}
// EdgeBetween returns the edge between nodes x and y.
func (g *UndirectedGraph) EdgeBetween(x, y graph.Node) graph.Edge {
return g.WeightedEdgeBetween(x, y)
}
// WeightedEdgeBetween returns the weighted edge between nodes x and y.
func (g *UndirectedGraph) WeightedEdgeBetween(x, y graph.Node) graph.WeightedEdge {
// We don't need to check if neigh exists because
// it's implicit in the edges access.
if !g.Has(x) {
@@ -211,24 +195,6 @@ func (g *UndirectedGraph) WeightedEdgeBetween(x, y graph.Node) graph.WeightedEdg
return g.edges[x.ID()][y.ID()]
}
// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge.
// If x and y are the same node or there is no joining edge between the two nodes the weight
// value returned is either the graph's absent or self value. Weight returns true if an edge
// exists between x and y or if x and y have the same ID, false otherwise.
func (g *UndirectedGraph) Weight(x, y graph.Node) (w float64, ok bool) {
xid := x.ID()
yid := y.ID()
if xid == yid {
return g.self, true
}
if n, ok := g.edges[xid]; ok {
if e, ok := n[yid]; ok {
return e.Weight(), true
}
}
return g.absent, false
}
// Degree returns the degree of n in g.
func (g *UndirectedGraph) Degree(n graph.Node) int {
if _, ok := g.nodes[n.ID()]; !ok {

View File

@@ -5,7 +5,6 @@
package simple
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
@@ -14,20 +13,19 @@ import (
var (
undirectedGraph = (*UndirectedGraph)(nil)
_ graph.Graph = undirectedGraph
_ graph.Undirected = undirectedGraph
_ graph.WeightedUndirected = undirectedGraph
_ graph.Graph = undirectedGraph
_ graph.Undirected = undirectedGraph
)
func TestAssertMutableNotDirected(t *testing.T) {
var g graph.UndirectedBuilder = NewUndirectedGraph(0, math.Inf(1))
var g graph.UndirectedBuilder = NewUndirectedGraph()
if _, ok := g.(graph.Directed); ok {
t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!")
}
}
func TestMaxID(t *testing.T) {
g := NewUndirectedGraph(0, math.Inf(1))
g := NewUndirectedGraph()
nodes := make(map[graph.Node]struct{})
for i := Node(0); i < 3; i++ {
g.AddNode(i)
@@ -54,7 +52,7 @@ func TestIssue123UndirectedGraph(t *testing.T) {
t.Errorf("unexpected panic: %v", r)
}
}()
g := NewUndirectedGraph(0, math.Inf(1))
g := NewUndirectedGraph()
n0 := g.NewNode()
g.AddNode(n0)

View File

@@ -0,0 +1,285 @@
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simple
import (
"fmt"
"gonum.org/v1/gonum/graph"
)
// WeightedDirectedGraph implements a generalized weighted directed graph.
type WeightedDirectedGraph struct {
nodes map[int64]graph.Node
from map[int64]map[int64]graph.WeightedEdge
to map[int64]map[int64]graph.WeightedEdge
self, absent float64
nodeIDs idSet
}
// NewWeightedDirectedGraph returns a WeightedDirectedGraph with the specified self and absent
// edge weight values.
func NewWeightedDirectedGraph(self, absent float64) *WeightedDirectedGraph {
return &WeightedDirectedGraph{
nodes: make(map[int64]graph.Node),
from: make(map[int64]map[int64]graph.WeightedEdge),
to: make(map[int64]map[int64]graph.WeightedEdge),
self: self,
absent: absent,
nodeIDs: newIDSet(),
}
}
// NewNode returns a new unique Node to be added to g. The Node's ID does
// not become valid in g until the Node is added to g.
func (g *WeightedDirectedGraph) NewNode() graph.Node {
if len(g.nodes) == 0 {
return Node(0)
}
if int64(len(g.nodes)) == maxInt {
panic("simple: cannot allocate node: no slot")
}
return Node(g.nodeIDs.newID())
}
// AddNode adds n to the graph. It panics if the added node ID matches an existing node ID.
func (g *WeightedDirectedGraph) AddNode(n graph.Node) {
if _, exists := g.nodes[n.ID()]; exists {
panic(fmt.Sprintf("simple: node ID collision: %d", n.ID()))
}
g.nodes[n.ID()] = n
g.from[n.ID()] = make(map[int64]graph.WeightedEdge)
g.to[n.ID()] = make(map[int64]graph.WeightedEdge)
g.nodeIDs.use(n.ID())
}
// RemoveNode removes n from the graph, as well as any edges attached to it. If the node
// is not in the graph it is a no-op.
func (g *WeightedDirectedGraph) RemoveNode(n graph.Node) {
if _, ok := g.nodes[n.ID()]; !ok {
return
}
delete(g.nodes, n.ID())
for from := range g.from[n.ID()] {
delete(g.to[from], n.ID())
}
delete(g.from, n.ID())
for to := range g.to[n.ID()] {
delete(g.from[to], n.ID())
}
delete(g.to, n.ID())
g.nodeIDs.release(n.ID())
}
// NewWeightedEdge returns a new weighted edge from the source to the destination node.
func (g *WeightedDirectedGraph) NewWeightedEdge(from, to graph.Node, weight float64) graph.WeightedEdge {
return &Edge{F: from, T: to, W: weight}
}
// SetWeightedEdge adds a weighted edge from one node to another. If the nodes do not exist, they are added.
// It will panic if the IDs of the e.From and e.To are equal.
func (g *WeightedDirectedGraph) SetWeightedEdge(e graph.WeightedEdge) {
var (
from = e.From()
fid = from.ID()
to = e.To()
tid = to.ID()
)
if fid == tid {
panic("simple: adding self edge")
}
if !g.Has(from) {
g.AddNode(from)
}
if !g.Has(to) {
g.AddNode(to)
}
g.from[fid][tid] = e
g.to[tid][fid] = e
}
// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist
// it is a no-op.
func (g *WeightedDirectedGraph) RemoveEdge(e graph.Edge) {
from, to := e.From(), e.To()
if _, ok := g.nodes[from.ID()]; !ok {
return
}
if _, ok := g.nodes[to.ID()]; !ok {
return
}
delete(g.from[from.ID()], to.ID())
delete(g.to[to.ID()], from.ID())
}
// Node returns the node in the graph with the given ID.
func (g *WeightedDirectedGraph) Node(id int64) graph.Node {
return g.nodes[id]
}
// Has returns whether the node exists within the graph.
func (g *WeightedDirectedGraph) Has(n graph.Node) bool {
_, ok := g.nodes[n.ID()]
return ok
}
// Nodes returns all the nodes in the graph.
func (g *WeightedDirectedGraph) Nodes() []graph.Node {
nodes := make([]graph.Node, len(g.from))
i := 0
for _, n := range g.nodes {
nodes[i] = n
i++
}
return nodes
}
// Edges returns all the edges in the graph.
func (g *WeightedDirectedGraph) Edges() []graph.Edge {
var edges []graph.Edge
for _, u := range g.nodes {
for _, e := range g.from[u.ID()] {
edges = append(edges, e)
}
}
return edges
}
// WeightedEdges returns all the weighted edges in the graph.
func (g *WeightedDirectedGraph) WeightedEdges() []graph.WeightedEdge {
var edges []graph.WeightedEdge
for _, u := range g.nodes {
for _, e := range g.from[u.ID()] {
edges = append(edges, e)
}
}
return edges
}
// From returns all nodes in g that can be reached directly from n.
func (g *WeightedDirectedGraph) From(n graph.Node) []graph.Node {
if _, ok := g.from[n.ID()]; !ok {
return nil
}
from := make([]graph.Node, len(g.from[n.ID()]))
i := 0
for id := range g.from[n.ID()] {
from[i] = g.nodes[id]
i++
}
return from
}
// To returns all nodes in g that can reach directly to n.
func (g *WeightedDirectedGraph) To(n graph.Node) []graph.Node {
if _, ok := g.from[n.ID()]; !ok {
return nil
}
to := make([]graph.Node, len(g.to[n.ID()]))
i := 0
for id := range g.to[n.ID()] {
to[i] = g.nodes[id]
i++
}
return to
}
// HasEdgeBetween returns whether an edge exists between nodes x and y without
// considering direction.
func (g *WeightedDirectedGraph) HasEdgeBetween(x, y graph.Node) bool {
xid := x.ID()
yid := y.ID()
if _, ok := g.nodes[xid]; !ok {
return false
}
if _, ok := g.nodes[yid]; !ok {
return false
}
if _, ok := g.from[xid][yid]; ok {
return true
}
_, ok := g.from[yid][xid]
return ok
}
// Edge returns the edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *WeightedDirectedGraph) Edge(u, v graph.Node) graph.Edge {
return g.WeightedEdge(u, v)
}
// WeightedEdge returns the weighted edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *WeightedDirectedGraph) WeightedEdge(u, v graph.Node) graph.WeightedEdge {
if _, ok := g.nodes[u.ID()]; !ok {
return nil
}
if _, ok := g.nodes[v.ID()]; !ok {
return nil
}
edge, ok := g.from[u.ID()][v.ID()]
if !ok {
return nil
}
return edge
}
// HasEdgeFromTo returns whether an edge exists in the graph from u to v.
func (g *WeightedDirectedGraph) HasEdgeFromTo(u, v graph.Node) bool {
if _, ok := g.nodes[u.ID()]; !ok {
return false
}
if _, ok := g.nodes[v.ID()]; !ok {
return false
}
if _, ok := g.from[u.ID()][v.ID()]; !ok {
return false
}
return true
}
// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge.
// If x and y are the same node or there is no joining edge between the two nodes the weight
// value returned is either the graph's absent or self value. Weight returns true if an edge
// exists between x and y or if x and y have the same ID, false otherwise.
func (g *WeightedDirectedGraph) Weight(x, y graph.Node) (w float64, ok bool) {
xid := x.ID()
yid := y.ID()
if xid == yid {
return g.self, true
}
if to, ok := g.from[xid]; ok {
if e, ok := to[yid]; ok {
return e.Weight(), true
}
}
return g.absent, false
}
// Degree returns the in+out degree of n in g.
func (g *WeightedDirectedGraph) Degree(n graph.Node) int {
if _, ok := g.nodes[n.ID()]; !ok {
return 0
}
return len(g.from[n.ID()]) + len(g.to[n.ID()])
}

View File

@@ -0,0 +1,67 @@
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simple
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
)
var (
weightedDirectedGraph = (*WeightedDirectedGraph)(nil)
_ graph.Graph = weightedDirectedGraph
_ graph.Directed = weightedDirectedGraph
_ graph.WeightedDirected = weightedDirectedGraph
)
// Tests Issue #27
func TestWeightedEdgeOvercounting(t *testing.T) {
g := generateDummyGraph()
if neigh := g.From(Node(Node(2))); len(neigh) != 2 {
t.Errorf("Node 2 has incorrect number of neighbors got neighbors %v (count %d), expected 2 neighbors {0,1}", neigh, len(neigh))
}
}
func generateDummyWeightedGraph() *WeightedDirectedGraph {
nodes := [4]struct{ srcID, targetID int }{
{2, 1},
{1, 0},
{2, 0},
{0, 2},
}
g := NewWeightedDirectedGraph(0, math.Inf(1))
for _, n := range nodes {
g.SetWeightedEdge(Edge{F: Node(n.srcID), T: Node(n.targetID), W: 1})
}
return g
}
// Test for issue #123 https://github.com/gonum/graph/issues/123
func TestIssue123WeightedDirectedGraph(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("unexpected panic: %v", r)
}
}()
g := NewWeightedDirectedGraph(0, math.Inf(1))
n0 := g.NewNode()
g.AddNode(n0)
n1 := g.NewNode()
g.AddNode(n1)
g.RemoveNode(n0)
n2 := g.NewNode()
g.AddNode(n2)
}

View File

@@ -0,0 +1,260 @@
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simple
import (
"fmt"
"gonum.org/v1/gonum/graph"
)
// WeightedUndirectedGraph implements a generalized weighted undirected graph.
type WeightedUndirectedGraph struct {
nodes map[int64]graph.Node
edges map[int64]map[int64]graph.WeightedEdge
self, absent float64
nodeIDs idSet
}
// NewWeightedUndirectedGraph returns an WeightedUndirectedGraph with the specified self and absent
// edge weight values.
func NewWeightedUndirectedGraph(self, absent float64) *WeightedUndirectedGraph {
return &WeightedUndirectedGraph{
nodes: make(map[int64]graph.Node),
edges: make(map[int64]map[int64]graph.WeightedEdge),
self: self,
absent: absent,
nodeIDs: newIDSet(),
}
}
// NewNode returns a new unique Node to be added to g. The Node's ID does
// not become valid in g until the Node is added to g.
func (g *WeightedUndirectedGraph) NewNode() graph.Node {
if len(g.nodes) == 0 {
return Node(0)
}
if int64(len(g.nodes)) == maxInt {
panic("simple: cannot allocate node: no slot")
}
return Node(g.nodeIDs.newID())
}
// AddNode adds n to the graph. It panics if the added node ID matches an existing node ID.
func (g *WeightedUndirectedGraph) AddNode(n graph.Node) {
if _, exists := g.nodes[n.ID()]; exists {
panic(fmt.Sprintf("simple: node ID collision: %d", n.ID()))
}
g.nodes[n.ID()] = n
g.edges[n.ID()] = make(map[int64]graph.WeightedEdge)
g.nodeIDs.use(n.ID())
}
// RemoveNode removes n from the graph, as well as any edges attached to it. If the node
// is not in the graph it is a no-op.
func (g *WeightedUndirectedGraph) RemoveNode(n graph.Node) {
if _, ok := g.nodes[n.ID()]; !ok {
return
}
delete(g.nodes, n.ID())
for from := range g.edges[n.ID()] {
delete(g.edges[from], n.ID())
}
delete(g.edges, n.ID())
g.nodeIDs.release(n.ID())
}
// NewWeightedEdge returns a new weighted edge from the source to the destination node.
func (g *WeightedUndirectedGraph) NewWeightedEdge(from, to graph.Node, weight float64) graph.WeightedEdge {
return &Edge{F: from, T: to, W: weight}
}
// SetWeightedEdge adds a weighted edge from one node to another. If the nodes do not exist, they are added.
// It will panic if the IDs of the e.From and e.To are equal.
func (g *WeightedUndirectedGraph) SetWeightedEdge(e graph.WeightedEdge) {
var (
from = e.From()
fid = from.ID()
to = e.To()
tid = to.ID()
)
if fid == tid {
panic("simple: adding self edge")
}
if !g.Has(from) {
g.AddNode(from)
}
if !g.Has(to) {
g.AddNode(to)
}
g.edges[fid][tid] = e
g.edges[tid][fid] = e
}
// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist
// it is a no-op.
func (g *WeightedUndirectedGraph) RemoveEdge(e graph.Edge) {
from, to := e.From(), e.To()
if _, ok := g.nodes[from.ID()]; !ok {
return
}
if _, ok := g.nodes[to.ID()]; !ok {
return
}
delete(g.edges[from.ID()], to.ID())
delete(g.edges[to.ID()], from.ID())
}
// Node returns the node in the graph with the given ID.
func (g *WeightedUndirectedGraph) Node(id int64) graph.Node {
return g.nodes[id]
}
// Has returns whether the node exists within the graph.
func (g *WeightedUndirectedGraph) Has(n graph.Node) bool {
_, ok := g.nodes[n.ID()]
return ok
}
// Nodes returns all the nodes in the graph.
func (g *WeightedUndirectedGraph) Nodes() []graph.Node {
nodes := make([]graph.Node, len(g.nodes))
i := 0
for _, n := range g.nodes {
nodes[i] = n
i++
}
return nodes
}
// Edges returns all the edges in the graph.
func (g *WeightedUndirectedGraph) Edges() []graph.Edge {
var edges []graph.Edge
seen := make(map[[2]int64]struct{})
for _, u := range g.edges {
for _, e := range u {
uid := e.From().ID()
vid := e.To().ID()
if _, ok := seen[[2]int64{uid, vid}]; ok {
continue
}
seen[[2]int64{uid, vid}] = struct{}{}
seen[[2]int64{vid, uid}] = struct{}{}
edges = append(edges, e)
}
}
return edges
}
// WeightedEdges returns all the weighted edges in the graph.
func (g *WeightedUndirectedGraph) WeightedEdges() []graph.WeightedEdge {
var edges []graph.WeightedEdge
seen := make(map[[2]int64]struct{})
for _, u := range g.edges {
for _, e := range u {
uid := e.From().ID()
vid := e.To().ID()
if _, ok := seen[[2]int64{uid, vid}]; ok {
continue
}
seen[[2]int64{uid, vid}] = struct{}{}
seen[[2]int64{vid, uid}] = struct{}{}
edges = append(edges, e)
}
}
return edges
}
// From returns all nodes in g that can be reached directly from n.
func (g *WeightedUndirectedGraph) From(n graph.Node) []graph.Node {
if !g.Has(n) {
return nil
}
nodes := make([]graph.Node, len(g.edges[n.ID()]))
i := 0
for from := range g.edges[n.ID()] {
nodes[i] = g.nodes[from]
i++
}
return nodes
}
// HasEdgeBetween returns whether an edge exists between nodes x and y.
func (g *WeightedUndirectedGraph) HasEdgeBetween(x, y graph.Node) bool {
_, ok := g.edges[x.ID()][y.ID()]
return ok
}
// Edge returns the edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *WeightedUndirectedGraph) Edge(u, v graph.Node) graph.Edge {
return g.WeightedEdgeBetween(u, v)
}
// WeightedEdge returns the weighted edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *WeightedUndirectedGraph) WeightedEdge(u, v graph.Node) graph.WeightedEdge {
return g.WeightedEdgeBetween(u, v)
}
// EdgeBetween returns the edge between nodes x and y.
func (g *WeightedUndirectedGraph) EdgeBetween(x, y graph.Node) graph.Edge {
return g.WeightedEdgeBetween(x, y)
}
// WeightedEdgeBetween returns the weighted edge between nodes x and y.
func (g *WeightedUndirectedGraph) WeightedEdgeBetween(x, y graph.Node) graph.WeightedEdge {
// We don't need to check if neigh exists because
// it's implicit in the edges access.
if !g.Has(x) {
return nil
}
return g.edges[x.ID()][y.ID()]
}
// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge.
// If x and y are the same node or there is no joining edge between the two nodes the weight
// value returned is either the graph's absent or self value. Weight returns true if an edge
// exists between x and y or if x and y have the same ID, false otherwise.
func (g *WeightedUndirectedGraph) Weight(x, y graph.Node) (w float64, ok bool) {
xid := x.ID()
yid := y.ID()
if xid == yid {
return g.self, true
}
if n, ok := g.edges[xid]; ok {
if e, ok := n[yid]; ok {
return e.Weight(), true
}
}
return g.absent, false
}
// Degree returns the degree of n in g.
func (g *WeightedUndirectedGraph) Degree(n graph.Node) int {
if _, ok := g.nodes[n.ID()]; !ok {
return 0
}
return len(g.edges[n.ID()])
}

View File

@@ -0,0 +1,69 @@
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simple
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
)
var (
weightedUndirectedGraph = (*WeightedUndirectedGraph)(nil)
_ graph.Graph = weightedUndirectedGraph
_ graph.Undirected = weightedUndirectedGraph
_ graph.WeightedUndirected = weightedUndirectedGraph
)
func TestAssertWeightedMutableNotDirected(t *testing.T) {
var g graph.UndirectedWeightedBuilder = NewWeightedUndirectedGraph(0, math.Inf(1))
if _, ok := g.(graph.Directed); ok {
t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!")
}
}
func TestWeightedMaxID(t *testing.T) {
g := NewWeightedUndirectedGraph(0, math.Inf(1))
nodes := make(map[graph.Node]struct{})
for i := Node(0); i < 3; i++ {
g.AddNode(i)
nodes[i] = struct{}{}
}
g.RemoveNode(Node(0))
delete(nodes, Node(0))
g.RemoveNode(Node(2))
delete(nodes, Node(2))
n := g.NewNode()
g.AddNode(n)
if !g.Has(n) {
t.Error("added node does not exist in graph")
}
if _, exists := nodes[n]; exists {
t.Errorf("Created already existing node id: %v", n.ID())
}
}
// Test for issue #123 https://github.com/gonum/graph/issues/123
func TestIssue123WeightedUndirectedGraph(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("unexpected panic: %v", r)
}
}()
g := NewWeightedUndirectedGraph(0, math.Inf(1))
n0 := g.NewNode()
g.AddNode(n0)
n1 := g.NewNode()
g.AddNode(n1)
g.RemoveNode(n0)
n2 := g.NewNode()
g.AddNode(n2)
}

View File

@@ -5,7 +5,6 @@
package topo
import (
"math"
"testing"
"gonum.org/v1/gonum/graph"
@@ -23,7 +22,7 @@ var (
)
func gnpDirected(n int, p float64) graph.Directed {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
gen.Gnp(g, n, p, nil)
return g
}

View File

@@ -5,7 +5,6 @@
package topo
import (
"math"
"reflect"
"sort"
"testing"
@@ -51,7 +50,7 @@ var vOrderTests = []struct {
func TestVertexOrdering(t *testing.T) {
for i, test := range vOrderTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -136,7 +135,7 @@ var bronKerboschTests = []struct {
func TestBronKerbosch(t *testing.T) {
for i, test := range bronKerboschTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {

View File

@@ -5,7 +5,6 @@
package topo
import (
"math"
"reflect"
"sort"
"testing"
@@ -85,7 +84,7 @@ var cyclesInTests = []struct {
func TestDirectedCyclesIn(t *testing.T) {
for i, test := range cyclesInTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs.
for u, e := range test.g {
// Add nodes that are not defined by an edge.

View File

@@ -5,7 +5,6 @@
package topo
import (
"math"
"reflect"
"sort"
"testing"
@@ -74,7 +73,7 @@ var undirectedCyclesInTests = []struct {
func TestUndirectedCyclesIn(t *testing.T) {
for i, test := range undirectedCyclesInTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs.
for u, e := range test.g {
// Add nodes that are not defined by an edge.

View File

@@ -5,7 +5,6 @@
package topo
import (
"math"
"reflect"
"sort"
"testing"
@@ -130,7 +129,7 @@ var tarjanTests = []struct {
func TestSort(t *testing.T) {
for i, test := range tarjanTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -161,7 +160,7 @@ func TestSort(t *testing.T) {
func TestTarjanSCC(t *testing.T) {
for i, test := range tarjanTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -288,7 +287,7 @@ var stabilizedSortTests = []struct {
func TestSortStabilized(t *testing.T) {
for i, test := range stabilizedSortTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {

View File

@@ -5,7 +5,6 @@
package topo
import (
"math"
"reflect"
"sort"
"testing"
@@ -16,7 +15,7 @@ import (
)
func TestIsPath(t *testing.T) {
dg := simple.NewDirectedGraph(0, math.Inf(1))
dg := simple.NewDirectedGraph()
if !IsPathIn(dg, nil) {
t.Error("IsPath returns false on nil path")
}
@@ -33,7 +32,7 @@ func TestIsPath(t *testing.T) {
if IsPathIn(dg, p) {
t.Error("IsPath returns true on bad path of length 2")
}
dg.SetEdge(simple.Edge{F: p[0], T: p[1], W: 1})
dg.SetEdge(simple.Edge{F: p[0], T: p[1]})
if !IsPathIn(dg, p) {
t.Error("IsPath returns false on correct path of length 2")
}
@@ -42,13 +41,13 @@ func TestIsPath(t *testing.T) {
t.Error("IsPath erroneously returns true for a reverse path")
}
p = []graph.Node{p[1], p[0], simple.Node(2)}
dg.SetEdge(simple.Edge{F: p[1], T: p[2], W: 1})
dg.SetEdge(simple.Edge{F: p[1], T: p[2]})
if !IsPathIn(dg, p) {
t.Error("IsPath does not find a correct path for path > 2 nodes")
}
ug := simple.NewUndirectedGraph(0, math.Inf(1))
ug.SetEdge(simple.Edge{F: p[1], T: p[0], W: 1})
ug.SetEdge(simple.Edge{F: p[1], T: p[2], W: 1})
ug := simple.NewUndirectedGraph()
ug.SetEdge(simple.Edge{F: p[1], T: p[0]})
ug.SetEdge(simple.Edge{F: p[1], T: p[2]})
if !IsPathIn(dg, p) {
t.Error("IsPath does not correctly account for undirected behavior")
}
@@ -69,7 +68,7 @@ var pathExistsInUndirectedTests = []struct {
func TestPathExistsInUndirected(t *testing.T) {
for i, test := range pathExistsInUndirectedTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
for u, e := range test.g {
if !g.Has(simple.Node(u)) {
@@ -108,7 +107,7 @@ var pathExistsInDirectedTests = []struct {
func TestPathExistsInDirected(t *testing.T) {
for i, test := range pathExistsInDirectedTests {
g := simple.NewDirectedGraph(0, math.Inf(1))
g := simple.NewDirectedGraph()
for u, e := range test.g {
if !g.Has(simple.Node(u)) {
@@ -145,7 +144,7 @@ var connectedComponentTests = []struct {
func TestConnectedComponents(t *testing.T) {
for i, test := range connectedComponentTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
for u, e := range test.g {
if !g.Has(simple.Node(u)) {

View File

@@ -6,7 +6,6 @@ package traverse
import (
"fmt"
"math"
"reflect"
"sort"
"testing"
@@ -134,7 +133,7 @@ var breadthFirstTests = []struct {
func TestBreadthFirst(t *testing.T) {
for i, test := range breadthFirstTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -222,7 +221,7 @@ var depthFirstTests = []struct {
func TestDepthFirst(t *testing.T) {
for i, test := range depthFirstTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.Has(simple.Node(u)) {
@@ -283,7 +282,7 @@ var walkAllTests = []struct {
func TestWalkAll(t *testing.T) {
for i, test := range walkAllTests {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
for u, e := range test.g {
if !g.Has(simple.Node(u)) {
@@ -366,7 +365,7 @@ var (
)
func gnpUndirected(n int, p float64) graph.Undirected {
g := simple.NewUndirectedGraph(0, math.Inf(1))
g := simple.NewUndirectedGraph()
gen.Gnp(g, n, p, nil)
return g
}

View File

@@ -4,36 +4,12 @@
package graph
// Undirect converts a directed graph to an undirected graph, resolving
// edge weight conflicts.
// Undirect converts a directed graph to an undirected graph.
type Undirect struct {
G Directed
// Absent is the value used to
// represent absent edge weights
// passed to Merge if the reverse
// edge is present.
Absent float64
// Merge defines how discordant edge
// weights in G are resolved. A merge
// is performed if at least one edge
// exists between the nodes being
// considered. The edges corresponding
// to the two weights are also passed,
// in the same order.
// The order of weight parameters
// passed to Merge is not defined, so
// the function should be commutative.
// If Merge is nil, the arithmetic
// mean is used to merge weights.
Merge func(x, y float64, xe, ye Edge) float64
}
var (
_ Undirected = Undirect{}
_ WeightedUndirected = Undirect{}
)
var _ Undirected = Undirect{}
// Has returns whether the node exists within the graph.
func (g Undirect) Has(n Node) bool { return g.G.Has(n) }
@@ -68,51 +44,118 @@ func (g Undirect) HasEdgeBetween(x, y Node) bool { return g.G.HasEdgeBetween(x,
// If an edge exists, the Edge returned is an EdgePair. The weight of
// the edge is determined by applying the Merge func to the weights of the
// edges between u and v.
func (g Undirect) Edge(u, v Node) Edge { return g.WeightedEdgeBetween(u, v) }
// WeightedEdge returns the weighted edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
// If an edge exists, the Edge returned is an EdgePair. The weight of
// the edge is determined by applying the Merge func to the weights of the
// edges between u and v.
func (g Undirect) WeightedEdge(u, v Node) WeightedEdge { return g.WeightedEdgeBetween(u, v) }
func (g Undirect) Edge(u, v Node) Edge { return g.EdgeBetween(u, v) }
// EdgeBetween returns the edge between nodes x and y. If an edge exists, the
// Edge returned is an EdgePair. The weight of the edge is determined by
// applying the Merge func to the weights of edges between x and y.
func (g Undirect) EdgeBetween(x, y Node) Edge {
return g.WeightedEdgeBetween(x, y)
}
// WeightedEdgeBetween returns the weighted edge between nodes x and y. If an edge exists, the
// Edge returned is an EdgePair. The weight of the edge is determined by
// applying the Merge func to the weights of edges between x and y.
func (g Undirect) WeightedEdgeBetween(x, y Node) WeightedEdge {
fe := g.G.Edge(x, y)
re := g.G.Edge(y, x)
if fe == nil && re == nil {
return nil
}
var f, r float64
if wg, ok := g.G.(WeightedDirected); ok {
f, ok = wg.Weight(x, y)
if !ok {
f = g.Absent
return EdgePair{fe, re}
}
// UndirectWeighted converts a directed weighted graph to an undirected weighted graph,
// resolving edge weight conflicts.
type UndirectWeighted struct {
G WeightedDirected
// Absent is the value used to
// represent absent edge weights
// passed to Merge if the reverse
// edge is present.
Absent float64
// Merge defines how discordant edge
// weights in G are resolved. A merge
// is performed if at least one edge
// exists between the nodes being
// considered. The edges corresponding
// to the two weights are also passed,
// in the same order.
// The order of weight parameters
// passed to Merge is not defined, so
// the function should be commutative.
// If Merge is nil, the arithmetic
// mean is used to merge weights.
Merge func(x, y float64, xe, ye Edge) float64
}
var (
_ Undirected = UndirectWeighted{}
_ WeightedUndirected = UndirectWeighted{}
)
// Has returns whether the node exists within the graph.
func (g UndirectWeighted) Has(n Node) bool { return g.G.Has(n) }
// Nodes returns all the nodes in the graph.
func (g UndirectWeighted) Nodes() []Node { return g.G.Nodes() }
// From returns all nodes in g that can be reached directly from u.
func (g UndirectWeighted) From(u Node) []Node {
var nodes []Node
seen := make(map[int64]struct{})
for _, n := range g.G.From(u) {
seen[n.ID()] = struct{}{}
nodes = append(nodes, n)
}
for _, n := range g.G.To(u) {
id := n.ID()
if _, ok := seen[id]; ok {
continue
}
r, ok = wg.Weight(y, x)
if !ok {
r = g.Absent
}
} else {
seen[n.ID()] = struct{}{}
nodes = append(nodes, n)
}
return nodes
}
// HasEdgeBetween returns whether an edge exists between nodes x and y.
func (g UndirectWeighted) HasEdgeBetween(x, y Node) bool { return g.G.HasEdgeBetween(x, y) }
// Edge returns the edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
// If an edge exists, the Edge returned is an EdgePair. The weight of
// the edge is determined by applying the Merge func to the weights of the
// edges between u and v.
func (g UndirectWeighted) Edge(u, v Node) Edge { return g.WeightedEdgeBetween(u, v) }
// WeightedEdge returns the weighted edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
// If an edge exists, the Edge returned is an EdgePair. The weight of
// the edge is determined by applying the Merge func to the weights of the
// edges between u and v.
func (g UndirectWeighted) WeightedEdge(u, v Node) WeightedEdge { return g.WeightedEdgeBetween(u, v) }
// EdgeBetween returns the edge between nodes x and y. If an edge exists, the
// Edge returned is an EdgePair. The weight of the edge is determined by
// applying the Merge func to the weights of edges between x and y.
func (g UndirectWeighted) EdgeBetween(x, y Node) Edge {
return g.WeightedEdgeBetween(x, y)
}
// WeightedEdgeBetween returns the weighted edge between nodes x and y. If an edge exists, the
// Edge returned is an EdgePair. The weight of the edge is determined by
// applying the Merge func to the weights of edges between x and y.
func (g UndirectWeighted) WeightedEdgeBetween(x, y Node) WeightedEdge {
fe := g.G.Edge(x, y)
re := g.G.Edge(y, x)
if fe == nil && re == nil {
return nil
}
f, ok := g.G.Weight(x, y)
if !ok {
f = g.Absent
if fe != nil {
f = fe.Weight()
}
}
r, ok := g.G.Weight(y, x)
if !ok {
r = g.Absent
if re != nil {
r = re.Weight()
}
}
var w float64
@@ -121,41 +164,26 @@ func (g Undirect) WeightedEdgeBetween(x, y Node) WeightedEdge {
} else {
w = g.Merge(f, r, fe, re)
}
return EdgePair{E: [2]Edge{fe, re}, W: w}
return WeightedEdgePair{EdgePair: [2]Edge{fe, re}, W: w}
}
// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge.
// If x and y are the same node the internal node weight is returned. If there is no joining
// edge between the two nodes the weight value returned is zero. Weight returns true if an edge
// exists between x and y or if x and y have the same ID, false otherwise.
func (g Undirect) Weight(x, y Node) (w float64, ok bool) {
func (g UndirectWeighted) Weight(x, y Node) (w float64, ok bool) {
fe := g.G.Edge(x, y)
re := g.G.Edge(y, x)
var f, r float64
if wg, wOk := g.G.(WeightedDirected); wOk {
var fOk, rOK bool
f, fOk = wg.Weight(x, y)
if !fOk {
f = g.Absent
}
r, rOK = wg.Weight(y, x)
if !rOK {
r = g.Absent
}
ok = fOk || rOK
} else {
f, fOk := g.G.Weight(x, y)
if !fOk {
f = g.Absent
if fe != nil {
f = fe.Weight()
ok = true
}
r = g.Absent
if re != nil {
r = re.Weight()
ok = true
}
}
r, rOK := g.G.Weight(y, x)
if !rOK {
r = g.Absent
}
ok = fOk || rOK
if g.Merge == nil {
return (f + r) / 2, ok
@@ -164,30 +192,33 @@ func (g Undirect) Weight(x, y Node) (w float64, ok bool) {
}
// EdgePair is an opposed pair of directed edges.
type EdgePair struct {
E [2]Edge
W float64
}
type EdgePair [2]Edge
// From returns the from node of the first non-nil edge, or nil.
func (e EdgePair) From() Node {
if e.E[0] != nil {
return e.E[0].From()
} else if e.E[1] != nil {
return e.E[1].From()
if e[0] != nil {
return e[0].From()
} else if e[1] != nil {
return e[1].From()
}
return nil
}
// To returns the to node of the first non-nil edge, or nil.
func (e EdgePair) To() Node {
if e.E[0] != nil {
return e.E[0].To()
} else if e.E[1] != nil {
return e.E[1].To()
if e[0] != nil {
return e[0].To()
} else if e[1] != nil {
return e[1].To()
}
return nil
}
// WeightedEdgePair is an opposed pair of directed edges.
type WeightedEdgePair struct {
EdgePair
W float64
}
// Weight returns the merged edge weights of the two edges.
func (e EdgePair) Weight() float64 { return e.W }
func (e WeightedEdgePair) Weight() float64 { return e.W }

View File

@@ -13,8 +13,15 @@ import (
"gonum.org/v1/gonum/mat"
)
var directedGraphs = []struct {
g func() graph.DirectedBuilder
type weightedDirectedBuilder interface {
graph.WeightedBuilder
graph.WeightedDirected
}
var weightedDirectedGraphs = []struct {
skipUnweighted bool
g func() weightedDirectedBuilder
edges []simple.Edge
absent float64
merge func(x, y float64, xe, ye graph.Edge) float64
@@ -22,7 +29,7 @@ var directedGraphs = []struct {
want mat.Matrix
}{
{
g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) },
g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) },
edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 2},
{F: simple.Node(1), T: simple.Node(0), W: 1},
@@ -35,7 +42,7 @@ var directedGraphs = []struct {
}),
},
{
g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) },
g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) },
edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 2},
{F: simple.Node(1), T: simple.Node(0), W: 1},
@@ -50,7 +57,9 @@ var directedGraphs = []struct {
}),
},
{
g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) },
skipUnweighted: true, // The min merge function cannot be used in the unweighted case.
g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) },
edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 2},
{F: simple.Node(1), T: simple.Node(0), W: 1},
@@ -64,7 +73,7 @@ var directedGraphs = []struct {
}),
},
{
g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) },
g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) },
edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 2},
{F: simple.Node(1), T: simple.Node(0), W: 1},
@@ -86,7 +95,7 @@ var directedGraphs = []struct {
}),
},
{
g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) },
g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) },
edges: []simple.Edge{
{F: simple.Node(0), T: simple.Node(1), W: 2},
{F: simple.Node(1), T: simple.Node(0), W: 1},
@@ -102,13 +111,16 @@ var directedGraphs = []struct {
}
func TestUndirect(t *testing.T) {
for _, test := range directedGraphs {
for i, test := range weightedDirectedGraphs {
if test.skipUnweighted {
continue
}
g := test.g()
for _, e := range test.edges {
g.SetEdge(e)
g.SetWeightedEdge(e)
}
src := graph.Undirect{G: g, Absent: test.absent, Merge: test.merge}
src := graph.Undirect{G: g}
dst := simple.NewUndirectedMatrixFrom(src.Nodes(), 0, 0, 0)
for _, u := range src.Nodes() {
for _, v := range src.From(u) {
@@ -116,11 +128,48 @@ func TestUndirect(t *testing.T) {
}
}
want := unit{test.want}
if !mat.Equal(dst.Matrix(), want) {
t.Errorf("unexpected result for case %d:\ngot:\n%.4v\nwant:\n%.4v", i,
mat.Formatted(dst.Matrix()),
mat.Formatted(want),
)
}
}
}
func TestUndirectWeighted(t *testing.T) {
for i, test := range weightedDirectedGraphs {
g := test.g()
for _, e := range test.edges {
g.SetWeightedEdge(e)
}
src := graph.UndirectWeighted{G: g, Absent: test.absent, Merge: test.merge}
dst := simple.NewUndirectedMatrixFrom(src.Nodes(), 0, 0, 0)
for _, u := range src.Nodes() {
for _, v := range src.From(u) {
dst.SetWeightedEdge(src.WeightedEdge(u, v))
}
}
if !mat.Equal(dst.Matrix(), test.want) {
t.Errorf("unexpected result:\ngot:\n%.4v\nwant:\n%.4v",
t.Errorf("unexpected result for case %d:\ngot:\n%.4v\nwant:\n%.4v", i,
mat.Formatted(dst.Matrix()),
mat.Formatted(test.want),
)
}
}
}
type unit struct {
mat.Matrix
}
func (m unit) At(i, j int) float64 {
v := m.Matrix.At(i, j)
if v == 0 {
return 0
}
return 1
}