mirror of
https://github.com/gonum/gonum.git
synced 2025-10-07 16:11:03 +08:00
graph/...: remove Weight method from Edge
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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() {
|
||||
|
@@ -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() {
|
||||
|
@@ -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)
|
||||
|
@@ -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() {
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
@@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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)) {
|
||||
|
@@ -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)) {
|
||||
|
@@ -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 (
|
||||
|
@@ -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))
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 (
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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))
|
||||
|
@@ -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},
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
285
graph/simple/weighted_directed.go
Normal file
285
graph/simple/weighted_directed.go
Normal 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()])
|
||||
}
|
67
graph/simple/weighted_directed_test.go
Normal file
67
graph/simple/weighted_directed_test.go
Normal 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)
|
||||
}
|
260
graph/simple/weighted_undirected.go
Normal file
260
graph/simple/weighted_undirected.go
Normal 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()])
|
||||
}
|
69
graph/simple/weighted_undirected_test.go
Normal file
69
graph/simple/weighted_undirected_test.go
Normal 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)
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
@@ -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)) {
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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)) {
|
||||
|
@@ -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)) {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 }
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user