mirror of
https://github.com/gonum/gonum.git
synced 2025-10-08 16:40:06 +08:00
graph/...: remove Weight method from Edge
This commit is contained in:
@@ -10,8 +10,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gonum.org/v1/gonum/graph"
|
||||||
"gonum.org/v1/gonum/graph/internal/ordered"
|
"gonum.org/v1/gonum/graph/internal/ordered"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph/simple"
|
"gonum.org/v1/gonum/graph/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,10 +24,10 @@ func ExampleProfile_simple() {
|
|||||||
// |/ \|
|
// |/ \|
|
||||||
// 1 5
|
// 1 5
|
||||||
//
|
//
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range smallDumbell {
|
for u, e := range smallDumbell {
|
||||||
for v := range e {
|
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
|
// 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() {
|
func init() {
|
||||||
friends = simple.NewUndirectedGraph(0, 0)
|
friends = simple.NewWeightedUndirectedGraph(0, 0)
|
||||||
for u, e := range middleEast.friends {
|
for u, e := range middleEast.friends {
|
||||||
// Ensure unconnected nodes are included.
|
// Ensure unconnected nodes are included.
|
||||||
if !friends.Has(simple.Node(u)) {
|
if !friends.Has(simple.Node(u)) {
|
||||||
friends.AddNode(simple.Node(u))
|
friends.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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 {
|
for u, e := range middleEast.enemies {
|
||||||
// Ensure unconnected nodes are included.
|
// Ensure unconnected nodes are included.
|
||||||
if !enemies.Has(simple.Node(u)) {
|
if !enemies.Has(simple.Node(u)) {
|
||||||
enemies.AddNode(simple.Node(u))
|
enemies.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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:
|
// 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.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: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.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.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.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.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.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]
|
// 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) {
|
func TestProfileUndirected(t *testing.T) {
|
||||||
for _, test := range communityUndirectedQTests {
|
for _, test := range communityUndirectedQTests {
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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)
|
testProfileUndirected(t, test, g)
|
||||||
p, err := Profile(fn, true, 1e-3, 0.1, 10)
|
}
|
||||||
if err != nil {
|
}
|
||||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
|
||||||
|
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
|
testProfileUndirected(t, test, g)
|
||||||
for i, d := range p {
|
}
|
||||||
var score float64
|
}
|
||||||
for i := 0; i < tries; i++ {
|
|
||||||
score, _ = fn(d.Low)
|
func testProfileUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
|
||||||
if score >= d.Score {
|
fn := ModularScore(g, Weight, 10, nil)
|
||||||
break
|
p, err := Profile(fn, true, 1e-3, 0.1, 10)
|
||||||
}
|
if err != nil {
|
||||||
}
|
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||||
if score < d.Score {
|
}
|
||||||
t.Errorf("%s: failed to recover low end score: got: %v want: %v", test.name, score, d.Score)
|
|
||||||
}
|
const tries = 1000
|
||||||
if i != 0 && d.Score >= p[i-1].Score {
|
for i, d := range p {
|
||||||
t.Errorf("%s: not monotonically decreasing: %v -> %v", test.name, p[i-1], d)
|
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) {
|
func TestProfileDirected(t *testing.T) {
|
||||||
for _, test := range communityDirectedQTests {
|
for _, test := range communityDirectedQTests {
|
||||||
g := simple.NewDirectedGraph(0, 0)
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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)
|
testProfileDirected(t, test, g)
|
||||||
p, err := Profile(fn, true, 1e-3, 0.1, 10)
|
}
|
||||||
if err != nil {
|
}
|
||||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
|
||||||
|
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
|
testProfileDirected(t, test, g)
|
||||||
for i, d := range p {
|
}
|
||||||
var score float64
|
}
|
||||||
for i := 0; i < tries; i++ {
|
|
||||||
score, _ = fn(d.Low)
|
func testProfileDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
|
||||||
if score >= d.Score {
|
fn := ModularScore(g, Weight, 10, nil)
|
||||||
break
|
p, err := Profile(fn, true, 1e-3, 0.1, 10)
|
||||||
}
|
if err != nil {
|
||||||
}
|
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||||
if score < d.Score {
|
}
|
||||||
t.Errorf("%s: failed to recover low end score: got: %v want: %v", test.name, score, d.Score)
|
|
||||||
}
|
const tries = 1000
|
||||||
if i != 0 && d.Score >= p[i-1].Score {
|
for i, d := range p {
|
||||||
t.Errorf("%s: not monotonically decreasing: %v -> %v", test.name, p[i-1], d)
|
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
|
// 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 {
|
func positiveWeightFuncFor(g graph.Graph) func(x, y graph.Node) float64 {
|
||||||
if wg, ok := g.(graph.Weighted); ok {
|
if wg, ok := g.(graph.Weighted); ok {
|
||||||
return func(x, y graph.Node) float64 {
|
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 {
|
if e == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
w := e.Weight()
|
return 1
|
||||||
if w < 0 {
|
|
||||||
panic(negativeWeight)
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// negativeWeightFuncFor returns a constructed weight function for the
|
// 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 {
|
func negativeWeightFuncFor(g graph.Graph) func(x, y graph.Node) float64 {
|
||||||
if wg, ok := g.(graph.Weighted); ok {
|
if wg, ok := g.(graph.Weighted); ok {
|
||||||
return func(x, y graph.Node) float64 {
|
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 {
|
if e == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
w := e.Weight()
|
return 1
|
||||||
if w > 0 {
|
|
||||||
panic(positiveWeight)
|
|
||||||
}
|
|
||||||
return -w
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -647,12 +647,32 @@ func TestLouvainDirectedMultiplex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNonContiguousDirectedMultiplex(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{
|
for _, e := range []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
||||||
} {
|
} {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
@@ -677,7 +697,7 @@ func directedMultiplexFrom(raw []layer) (DirectedLayers, []float64, error) {
|
|||||||
var layers []graph.Directed
|
var layers []graph.Directed
|
||||||
var weights []float64
|
var weights []float64
|
||||||
for _, l := range raw {
|
for _, l := range raw {
|
||||||
g := simple.NewDirectedGraph(0, 0)
|
g := simple.NewWeightedDirectedGraph(0, 0)
|
||||||
for u, e := range l.g {
|
for u, e := range l.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -688,7 +708,7 @@ func directedMultiplexFrom(raw []layer) (DirectedLayers, []float64, error) {
|
|||||||
if l.edgeWeight != 0 {
|
if l.edgeWeight != 0 {
|
||||||
w = l.edgeWeight
|
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)
|
layers = append(layers, g)
|
||||||
|
@@ -17,13 +17,15 @@ import (
|
|||||||
"gonum.org/v1/gonum/graph/simple"
|
"gonum.org/v1/gonum/graph/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
var communityDirectedQTests = []struct {
|
type communityDirectedQTest struct {
|
||||||
name string
|
name string
|
||||||
g []intset
|
g []intset
|
||||||
structures []structure
|
structures []structure
|
||||||
|
|
||||||
wantLevels []level
|
wantLevels []level
|
||||||
}{
|
}
|
||||||
|
|
||||||
|
var communityDirectedQTests = []communityDirectedQTest{
|
||||||
{
|
{
|
||||||
name: "simple_directed",
|
name: "simple_directed",
|
||||||
g: simpleDirected,
|
g: simpleDirected,
|
||||||
@@ -199,194 +201,258 @@ var communityDirectedQTests = []struct {
|
|||||||
|
|
||||||
func TestCommunityQDirected(t *testing.T) {
|
func TestCommunityQDirected(t *testing.T) {
|
||||||
for _, test := range communityDirectedQTests {
|
for _, test := range communityDirectedQTests {
|
||||||
g := simple.NewDirectedGraph(0, 0)
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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))
|
testCommunityQDirected(t, test, g)
|
||||||
for i, c := range structure.memberships {
|
}
|
||||||
for n := range c {
|
}
|
||||||
communities[i] = append(communities[i], simple.Node(n))
|
|
||||||
}
|
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)
|
for v := range e {
|
||||||
if !floats.EqualWithinAbsOrRel(got, structure.want, structure.tol, structure.tol) && !math.IsNaN(structure.want) {
|
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestCommunityDeltaQDirected(t *testing.T) {
|
||||||
tests:
|
|
||||||
for _, test := range communityDirectedQTests {
|
for _, test := range communityDirectedQTests {
|
||||||
g := simple.NewDirectedGraph(0, 0)
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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
|
testCommunityDeltaQDirected(t, test, g)
|
||||||
for _, structure := range test.structures {
|
}
|
||||||
communityOf := make(map[int64]int)
|
}
|
||||||
communities := make([][]graph.Node, len(structure.memberships))
|
|
||||||
|
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 i, c := range structure.memberships {
|
||||||
for n := range c {
|
for n := range c {
|
||||||
n := int64(n)
|
n := int64(n)
|
||||||
communityOf[n] = i
|
if n == target.ID() {
|
||||||
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()] {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
connected := false
|
migrated[i] = append(migrated[i], simple.Node(n))
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
sort.Sort(ordered.ByID(migrated[i]))
|
||||||
|
}
|
||||||
|
|
||||||
if !floats.EqualWithinAbsOrRel(got, want, structure.tol, structure.tol) || gotDst != wantDst {
|
for i, c := range structure.memberships {
|
||||||
t.Errorf("unexpected result moving n=%d in c=%d of %s/%.4v: got: %.4v,%d want: %.4v,%d"+
|
if i == communityOf[target.ID()] {
|
||||||
"\n\t%v\n\t%v",
|
continue
|
||||||
target.ID(), communityOf[target.ID()], test.name, structure.resolution, got, gotDst, want, wantDst,
|
|
||||||
communities, migrated)
|
|
||||||
}
|
}
|
||||||
if gotSrc.community != communityOf[target.ID()] {
|
connected := false
|
||||||
t.Errorf("unexpected source community index: got: %d want: %d", gotSrc, communityOf[target.ID()])
|
for n := range c {
|
||||||
} else if communities[gotSrc.community][gotSrc.node].ID() != target.ID() {
|
if g.HasEdgeBetween(simple.Node(n), target) {
|
||||||
wantNodeIdx := -1
|
connected = true
|
||||||
for i, n := range communities[gotSrc.community] {
|
break
|
||||||
if n.ID() == target.ID() {
|
|
||||||
wantNodeIdx = i
|
|
||||||
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) {
|
func TestReduceQConsistencyDirected(t *testing.T) {
|
||||||
tests:
|
|
||||||
for _, test := range communityDirectedQTests {
|
for _, test := range communityDirectedQTests {
|
||||||
g := simple.NewDirectedGraph(0, 0)
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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 {
|
testReduceQConsistencyDirected(t, test, g)
|
||||||
if math.IsNaN(structure.want) {
|
}
|
||||||
continue tests
|
}
|
||||||
}
|
|
||||||
|
|
||||||
communities := make([][]graph.Node, len(structure.memberships))
|
func TestReduceQConsistencyWeightedDirected(t *testing.T) {
|
||||||
for i, c := range structure.memberships {
|
for _, test := range communityDirectedQTests {
|
||||||
for n := range c {
|
g := simple.NewWeightedDirectedGraph(0, 0)
|
||||||
communities[i] = append(communities[i], simple.Node(n))
|
for u, e := range test.g {
|
||||||
}
|
// Add nodes that are not defined by an edge.
|
||||||
sort.Sort(ordered.ByID(communities[i]))
|
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)
|
testReduceQConsistencyDirected(t, test, g)
|
||||||
gQnull := Q(g, nil, 1)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cg0 := reduceDirected(g, nil)
|
func testReduceQConsistencyDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
|
||||||
cg0Qnull := Q(cg0, cg0.Structure(), 1)
|
for _, structure := range test.structures {
|
||||||
if !floats.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
|
if math.IsNaN(structure.want) {
|
||||||
t.Errorf("disagreement between null Q from method: %v and function: %v", cg0Qnull, gQnull)
|
return
|
||||||
}
|
}
|
||||||
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)
|
communities := make([][]graph.Node, len(structure.memberships))
|
||||||
cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
|
for i, c := range structure.memberships {
|
||||||
if !floats.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
|
for n := range c {
|
||||||
t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ)
|
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
|
name string
|
||||||
g []intset
|
g []intset
|
||||||
structures []moveStructures
|
structures []moveStructures
|
||||||
}{
|
}
|
||||||
|
|
||||||
|
var localDirectedMoveTests = []localDirectedMoveTest{
|
||||||
{
|
{
|
||||||
name: "blondel",
|
name: "blondel",
|
||||||
g: blondel,
|
g: blondel,
|
||||||
@@ -431,39 +497,60 @@ var localDirectedMoveTests = []struct {
|
|||||||
|
|
||||||
func TestMoveLocalDirected(t *testing.T) {
|
func TestMoveLocalDirected(t *testing.T) {
|
||||||
for _, test := range localDirectedMoveTests {
|
for _, test := range localDirectedMoveTests {
|
||||||
g := simple.NewDirectedGraph(0, 0)
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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 {
|
testMoveLocalDirected(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))
|
func TestMoveLocalWeightedDirected(t *testing.T) {
|
||||||
}
|
for _, test := range localDirectedMoveTests {
|
||||||
sort.Sort(ordered.ByID(communities[i]))
|
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)
|
func testMoveLocalDirected(t *testing.T, test localDirectedMoveTest, g graph.Directed) {
|
||||||
for _, n := range structure.targetNodes {
|
for _, structure := range test.structures {
|
||||||
dQ, dst, src := l.deltaQ(n)
|
communities := make([][]graph.Node, len(structure.memberships))
|
||||||
if dQ > 0 {
|
for i, c := range structure.memberships {
|
||||||
before := Q(r, l.communities, structure.resolution)
|
for n := range c {
|
||||||
l.move(dst, src)
|
communities[i] = append(communities[i], simple.Node(n))
|
||||||
after := Q(r, l.communities, structure.resolution)
|
}
|
||||||
want := after - before
|
sort.Sort(ordered.ByID(communities[i]))
|
||||||
if !floats.EqualWithinAbsOrRel(dQ, want, structure.tol, structure.tol) {
|
}
|
||||||
t.Errorf("unexpected deltaQ: got: %v want: %v", dQ, want)
|
|
||||||
}
|
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) {
|
func TestModularizeDirected(t *testing.T) {
|
||||||
const louvainIterations = 20
|
|
||||||
|
|
||||||
for _, test := range communityDirectedQTests {
|
for _, test := range communityDirectedQTests {
|
||||||
g := simple.NewDirectedGraph(0, 0)
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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 {
|
testModularizeDirected(t, test, g)
|
||||||
panic("bad test: expect resolution=1")
|
}
|
||||||
}
|
}
|
||||||
want := make([][]graph.Node, len(test.structures[0].memberships))
|
|
||||||
for i, c := range test.structures[0].memberships {
|
func TestModularizeWeightedDirected(t *testing.T) {
|
||||||
for n := range c {
|
for _, test := range communityDirectedQTests {
|
||||||
want[i] = append(want[i], simple.Node(n))
|
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]))
|
for v := range e {
|
||||||
}
|
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gotCommunities := got.Communities()
|
testModularizeDirected(t, test, g)
|
||||||
for _, c := range gotCommunities {
|
}
|
||||||
sort.Sort(ordered.ByID(c))
|
}
|
||||||
}
|
|
||||||
sort.Sort(ordered.BySliceIDs(gotCommunities))
|
func testModularizeDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
|
||||||
if !reflect.DeepEqual(gotCommunities, want) {
|
const louvainIterations = 20
|
||||||
t.Errorf("unexpected community membership for %s Q=%.4v:\n\tgot: %v\n\twant:%v",
|
|
||||||
test.name, bestQ, gotCommunities, want)
|
if test.structures[0].resolution != 1 {
|
||||||
continue
|
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) {
|
if math.IsNaN(q) {
|
||||||
// Use an equalable flag value in place of NaN.
|
// Don't try again for non-connected case.
|
||||||
q = math.Inf(-1)
|
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) {
|
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{
|
for _, e := range []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
||||||
} {
|
} {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
|
@@ -229,8 +229,8 @@ func hasNegative(f []float64) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dupGraph = simple.NewUndirectedGraph(0, 0)
|
dupGraph = simple.NewUndirectedGraph()
|
||||||
dupGraphDirected = simple.NewDirectedGraph(0, 0)
|
dupGraphDirected = simple.NewDirectedGraph()
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@@ -616,12 +616,32 @@ func TestLouvainMultiplex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNonContiguousUndirectedMultiplex(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{
|
for _, e := range []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
||||||
} {
|
} {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
@@ -646,7 +666,7 @@ func undirectedMultiplexFrom(raw []layer) (UndirectedLayers, []float64, error) {
|
|||||||
var layers []graph.Undirected
|
var layers []graph.Undirected
|
||||||
var weights []float64
|
var weights []float64
|
||||||
for _, l := range raw {
|
for _, l := range raw {
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewWeightedUndirectedGraph(0, 0)
|
||||||
for u, e := range l.g {
|
for u, e := range l.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -657,7 +677,7 @@ func undirectedMultiplexFrom(raw []layer) (UndirectedLayers, []float64, error) {
|
|||||||
if l.edgeWeight != 0 {
|
if l.edgeWeight != 0 {
|
||||||
w = l.edgeWeight
|
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)
|
layers = append(layers, g)
|
||||||
|
@@ -17,13 +17,15 @@ import (
|
|||||||
"gonum.org/v1/gonum/graph/simple"
|
"gonum.org/v1/gonum/graph/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
var communityUndirectedQTests = []struct {
|
type communityUndirectedQTest struct {
|
||||||
name string
|
name string
|
||||||
g []intset
|
g []intset
|
||||||
structures []structure
|
structures []structure
|
||||||
|
|
||||||
wantLevels []level
|
wantLevels []level
|
||||||
}{
|
}
|
||||||
|
|
||||||
|
var communityUndirectedQTests = []communityUndirectedQTest{
|
||||||
// The java reference implementation is available from http://www.ludowaltman.nl/slm/.
|
// The java reference implementation is available from http://www.ludowaltman.nl/slm/.
|
||||||
{
|
{
|
||||||
name: "unconnected",
|
name: "unconnected",
|
||||||
@@ -258,194 +260,258 @@ var communityUndirectedQTests = []struct {
|
|||||||
|
|
||||||
func TestCommunityQUndirected(t *testing.T) {
|
func TestCommunityQUndirected(t *testing.T) {
|
||||||
for _, test := range communityUndirectedQTests {
|
for _, test := range communityUndirectedQTests {
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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))
|
testCommunityQUndirected(t, test, g)
|
||||||
for i, c := range structure.memberships {
|
}
|
||||||
for n := range c {
|
}
|
||||||
communities[i] = append(communities[i], simple.Node(n))
|
|
||||||
}
|
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)
|
for v := range e {
|
||||||
if !floats.EqualWithinAbsOrRel(got, structure.want, structure.tol, structure.tol) && !math.IsNaN(structure.want) {
|
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestCommunityDeltaQUndirected(t *testing.T) {
|
||||||
tests:
|
|
||||||
for _, test := range communityUndirectedQTests {
|
for _, test := range communityUndirectedQTests {
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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
|
testCommunityDeltaQUndirected(t, test, g)
|
||||||
for _, structure := range test.structures {
|
}
|
||||||
communityOf := make(map[int64]int)
|
}
|
||||||
communities := make([][]graph.Node, len(structure.memberships))
|
|
||||||
|
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 i, c := range structure.memberships {
|
||||||
for n := range c {
|
for n := range c {
|
||||||
n := int64(n)
|
n := int64(n)
|
||||||
communityOf[n] = i
|
if n == target.ID() {
|
||||||
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()] {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
connected := false
|
migrated[i] = append(migrated[i], simple.Node(n))
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
sort.Sort(ordered.ByID(migrated[i]))
|
||||||
|
}
|
||||||
|
|
||||||
if !floats.EqualWithinAbsOrRel(got, want, structure.tol, structure.tol) || gotDst != wantDst {
|
for i, c := range structure.memberships {
|
||||||
t.Errorf("unexpected result moving n=%d in c=%d of %s/%.4v: got: %.4v,%d want: %.4v,%d"+
|
if i == communityOf[target.ID()] {
|
||||||
"\n\t%v\n\t%v",
|
continue
|
||||||
target.ID(), communityOf[target.ID()], test.name, structure.resolution, got, gotDst, want, wantDst,
|
|
||||||
communities, migrated)
|
|
||||||
}
|
}
|
||||||
if gotSrc.community != communityOf[target.ID()] {
|
connected := false
|
||||||
t.Errorf("unexpected source community index: got: %d want: %d", gotSrc, communityOf[target.ID()])
|
for n := range c {
|
||||||
} else if communities[gotSrc.community][gotSrc.node].ID() != target.ID() {
|
if g.HasEdgeBetween(simple.Node(n), target) {
|
||||||
wantNodeIdx := -1
|
connected = true
|
||||||
for i, n := range communities[gotSrc.community] {
|
break
|
||||||
if n.ID() == target.ID() {
|
|
||||||
wantNodeIdx = i
|
|
||||||
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) {
|
func TestReduceQConsistencyUndirected(t *testing.T) {
|
||||||
tests:
|
|
||||||
for _, test := range communityUndirectedQTests {
|
for _, test := range communityUndirectedQTests {
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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 {
|
testReduceQConsistencyUndirected(t, test, g)
|
||||||
if math.IsNaN(structure.want) {
|
}
|
||||||
continue tests
|
}
|
||||||
}
|
|
||||||
|
|
||||||
communities := make([][]graph.Node, len(structure.memberships))
|
func TestReduceQConsistencyWeightedUndirected(t *testing.T) {
|
||||||
for i, c := range structure.memberships {
|
for _, test := range communityUndirectedQTests {
|
||||||
for n := range c {
|
g := simple.NewWeightedUndirectedGraph(0, 0)
|
||||||
communities[i] = append(communities[i], simple.Node(n))
|
for u, e := range test.g {
|
||||||
}
|
// Add nodes that are not defined by an edge.
|
||||||
sort.Sort(ordered.ByID(communities[i]))
|
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)
|
testReduceQConsistencyUndirected(t, test, g)
|
||||||
gQnull := Q(g, nil, 1)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cg0 := reduceUndirected(g, nil)
|
func testReduceQConsistencyUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
|
||||||
cg0Qnull := Q(cg0, cg0.Structure(), 1)
|
for _, structure := range test.structures {
|
||||||
if !floats.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
|
if math.IsNaN(structure.want) {
|
||||||
t.Errorf("disagreement between null Q from method: %v and function: %v", cg0Qnull, gQnull)
|
return
|
||||||
}
|
}
|
||||||
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)
|
communities := make([][]graph.Node, len(structure.memberships))
|
||||||
cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
|
for i, c := range structure.memberships {
|
||||||
if !floats.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
|
for n := range c {
|
||||||
t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ)
|
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
|
name string
|
||||||
g []intset
|
g []intset
|
||||||
structures []moveStructures
|
structures []moveStructures
|
||||||
}{
|
}
|
||||||
|
|
||||||
|
var localUndirectedMoveTests = []localUndirectedMoveTest{
|
||||||
{
|
{
|
||||||
name: "blondel",
|
name: "blondel",
|
||||||
g: blondel,
|
g: blondel,
|
||||||
@@ -490,39 +556,60 @@ var localUndirectedMoveTests = []struct {
|
|||||||
|
|
||||||
func TestMoveLocalUndirected(t *testing.T) {
|
func TestMoveLocalUndirected(t *testing.T) {
|
||||||
for _, test := range localUndirectedMoveTests {
|
for _, test := range localUndirectedMoveTests {
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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 {
|
testMoveLocalUndirected(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))
|
func TestMoveLocalWeightedUndirected(t *testing.T) {
|
||||||
}
|
for _, test := range localUndirectedMoveTests {
|
||||||
sort.Sort(ordered.ByID(communities[i]))
|
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)
|
func testMoveLocalUndirected(t *testing.T, test localUndirectedMoveTest, g graph.Undirected) {
|
||||||
for _, n := range structure.targetNodes {
|
for _, structure := range test.structures {
|
||||||
dQ, dst, src := l.deltaQ(n)
|
communities := make([][]graph.Node, len(structure.memberships))
|
||||||
if dQ > 0 {
|
for i, c := range structure.memberships {
|
||||||
before := Q(r, l.communities, structure.resolution)
|
for n := range c {
|
||||||
l.move(dst, src)
|
communities[i] = append(communities[i], simple.Node(n))
|
||||||
after := Q(r, l.communities, structure.resolution)
|
}
|
||||||
want := after - before
|
sort.Sort(ordered.ByID(communities[i]))
|
||||||
if !floats.EqualWithinAbsOrRel(dQ, want, structure.tol, structure.tol) {
|
}
|
||||||
t.Errorf("unexpected deltaQ: got: %v want: %v", dQ, want)
|
|
||||||
}
|
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) {
|
func TestModularizeUndirected(t *testing.T) {
|
||||||
const louvainIterations = 20
|
|
||||||
|
|
||||||
for _, test := range communityUndirectedQTests {
|
for _, test := range communityUndirectedQTests {
|
||||||
g := simple.NewUndirectedGraph(0, 0)
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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 {
|
testModularizeUndirected(t, test, g)
|
||||||
panic("bad test: expect resolution=1")
|
}
|
||||||
}
|
}
|
||||||
want := make([][]graph.Node, len(test.structures[0].memberships))
|
|
||||||
for i, c := range test.structures[0].memberships {
|
func TestModularizeWeightedUndirected(t *testing.T) {
|
||||||
for n := range c {
|
for _, test := range communityUndirectedQTests {
|
||||||
want[i] = append(want[i], simple.Node(n))
|
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]))
|
for v := range e {
|
||||||
}
|
g.SetWeightedEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1})
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gotCommunities := got.Communities()
|
testModularizeUndirected(t, test, g)
|
||||||
for _, c := range gotCommunities {
|
}
|
||||||
sort.Sort(ordered.ByID(c))
|
}
|
||||||
}
|
|
||||||
sort.Sort(ordered.BySliceIDs(gotCommunities))
|
func testModularizeUndirected(t *testing.T, test communityUndirectedQTest, g graph.Undirected) {
|
||||||
if !reflect.DeepEqual(gotCommunities, want) {
|
const louvainIterations = 20
|
||||||
t.Errorf("unexpected community membership for %s Q=%.4v:\n\tgot: %v\n\twant:%v",
|
|
||||||
test.name, bestQ, gotCommunities, want)
|
if test.structures[0].resolution != 1 {
|
||||||
continue
|
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) {
|
if math.IsNaN(q) {
|
||||||
// Use an equalable flag value in place of NaN.
|
// Don't try again for non-connected case.
|
||||||
q = math.Inf(-1)
|
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) {
|
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{
|
for _, e := range []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
{F: simple.Node(4), T: simple.Node(5), W: 1},
|
||||||
} {
|
} {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
|
@@ -110,7 +110,7 @@ type dotDirectedGraph struct {
|
|||||||
// newDotDirectedGraph returns a new directed capable of creating user-defined
|
// newDotDirectedGraph returns a new directed capable of creating user-defined
|
||||||
// nodes and edges.
|
// nodes and edges.
|
||||||
func newDotDirectedGraph() *dotDirectedGraph {
|
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.
|
// 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-
|
// newDotUndirectedGraph returns a new undirected capable of creating user-
|
||||||
// defined nodes and edges.
|
// defined nodes and edges.
|
||||||
func newDotUndirectedGraph() *dotUndirectedGraph {
|
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.
|
// NewNode adds a new node with a unique node ID to the graph.
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package dot
|
package dot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph"
|
"gonum.org/v1/gonum/graph"
|
||||||
@@ -55,7 +54,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func directedGraphFrom(g []intset) graph.Directed {
|
func directedGraphFrom(g []intset) graph.Directed {
|
||||||
dg := simple.NewDirectedGraph(0, math.Inf(1))
|
dg := simple.NewDirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
for v := range e {
|
for v := range e {
|
||||||
dg.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
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 {
|
func undirectedGraphFrom(g []intset) graph.Graph {
|
||||||
dg := simple.NewUndirectedGraph(0, math.Inf(1))
|
dg := simple.NewUndirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
for v := range e {
|
for v := range e {
|
||||||
dg.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
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 (n namedNode) DOTID() string { return n.name }
|
||||||
|
|
||||||
func directedNamedIDGraphFrom(g []intset) graph.Directed {
|
func directedNamedIDGraphFrom(g []intset) graph.Directed {
|
||||||
dg := simple.NewDirectedGraph(0, math.Inf(1))
|
dg := simple.NewDirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
nu := namedNode{id: u, name: alpha[u : u+1]}
|
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 {
|
func undirectedNamedIDGraphFrom(g []intset) graph.Graph {
|
||||||
dg := simple.NewUndirectedGraph(0, math.Inf(1))
|
dg := simple.NewUndirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
nu := namedNode{id: u, name: alpha[u : u+1]}
|
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 (n attrNode) Attributes() []encoding.Attribute { return n.attr }
|
||||||
|
|
||||||
func directedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
|
func directedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
|
||||||
dg := simple.NewDirectedGraph(0, math.Inf(1))
|
dg := simple.NewDirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
var at []encoding.Attribute
|
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 {
|
func undirectedNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Graph {
|
||||||
dg := simple.NewUndirectedGraph(0, math.Inf(1))
|
dg := simple.NewUndirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
var at []encoding.Attribute
|
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 (n namedAttrNode) Attributes() []encoding.Attribute { return n.attr }
|
||||||
|
|
||||||
func directedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
|
func directedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Directed {
|
||||||
dg := simple.NewDirectedGraph(0, math.Inf(1))
|
dg := simple.NewDirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
var at []encoding.Attribute
|
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 {
|
func undirectedNamedIDNodeAttrGraphFrom(g []intset, attr [][]encoding.Attribute) graph.Graph {
|
||||||
dg := simple.NewUndirectedGraph(0, math.Inf(1))
|
dg := simple.NewUndirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
var at []encoding.Attribute
|
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 (e attrEdge) Attributes() []encoding.Attribute { return e.attr }
|
||||||
|
|
||||||
func directedEdgeAttrGraphFrom(g []intset, attr map[edge][]encoding.Attribute) graph.Directed {
|
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 {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
for v := range e {
|
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 {
|
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 {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
for v := range e {
|
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 {
|
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 {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
var at []encoding.Attribute
|
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 {
|
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 {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
var at []encoding.Attribute
|
var at []encoding.Attribute
|
||||||
@@ -337,10 +336,10 @@ type structuredGraph struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func undirectedStructuredGraphFrom(c []edge, g ...[]intset) graph.Graph {
|
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
|
var base int64
|
||||||
for i, sg := range g {
|
for i, sg := range g {
|
||||||
sub := simple.NewUndirectedGraph(0, math.Inf(1))
|
sub := simple.NewUndirectedGraph()
|
||||||
for u, e := range sg {
|
for u, e := range sg {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
for v := range e {
|
for v := range e {
|
||||||
@@ -382,7 +381,7 @@ func undirectedSubGraphFrom(g []intset, s map[int64][]intset) graph.Graph {
|
|||||||
var base int64
|
var base int64
|
||||||
subs := make(map[int64]subGraph)
|
subs := make(map[int64]subGraph)
|
||||||
for i, sg := range s {
|
for i, sg := range s {
|
||||||
sub := simple.NewUndirectedGraph(0, math.Inf(1))
|
sub := simple.NewUndirectedGraph()
|
||||||
for u, e := range sg {
|
for u, e := range sg {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
for v := range e {
|
for v := range e {
|
||||||
@@ -394,7 +393,7 @@ func undirectedSubGraphFrom(g []intset, s map[int64][]intset) graph.Graph {
|
|||||||
base += int64(len(sg))
|
base += int64(len(sg))
|
||||||
}
|
}
|
||||||
|
|
||||||
dg := simple.NewUndirectedGraph(0, math.Inf(1))
|
dg := simple.NewUndirectedGraph()
|
||||||
for u, e := range g {
|
for u, e := range g {
|
||||||
u := int64(u)
|
u := int64(u)
|
||||||
var nu graph.Node
|
var nu graph.Node
|
||||||
|
@@ -155,7 +155,7 @@ type directedGraph struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newDirectedGraph() *directedGraph {
|
func newDirectedGraph() *directedGraph {
|
||||||
return &directedGraph{DirectedGraph: simple.NewDirectedGraph(0, 0)}
|
return &directedGraph{DirectedGraph: simple.NewDirectedGraph()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *directedGraph) NewNode() graph.Node {
|
func (g *directedGraph) NewNode() graph.Node {
|
||||||
|
@@ -15,13 +15,15 @@ type Node interface {
|
|||||||
type Edge interface {
|
type Edge interface {
|
||||||
From() Node
|
From() Node
|
||||||
To() 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
|
// of the edge is given from -> to, otherwise the edge is semantically
|
||||||
// unordered.
|
// unordered.
|
||||||
type WeightedEdge Edge
|
type WeightedEdge interface {
|
||||||
|
Edge
|
||||||
|
Weight() float64
|
||||||
|
}
|
||||||
|
|
||||||
// Graph is a generalized graph.
|
// Graph is a generalized graph.
|
||||||
type Graph interface {
|
type Graph interface {
|
||||||
@@ -143,6 +145,22 @@ type EdgeAdder interface {
|
|||||||
SetEdge(e Edge)
|
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.
|
// EdgeRemover is an interface for removing nodes from a graph.
|
||||||
type EdgeRemover interface {
|
type EdgeRemover interface {
|
||||||
// RemoveEdge removes the given edge, leaving the
|
// RemoveEdge removes the given edge, leaving the
|
||||||
@@ -157,29 +175,42 @@ type Builder interface {
|
|||||||
EdgeAdder
|
EdgeAdder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WeightedBuilder is a graph that can have nodes and weighted edges added.
|
||||||
|
type WeightedBuilder interface {
|
||||||
|
NodeAdder
|
||||||
|
WeightedEdgeAdder
|
||||||
|
}
|
||||||
|
|
||||||
// UndirectedBuilder is an undirected graph builder.
|
// UndirectedBuilder is an undirected graph builder.
|
||||||
type UndirectedBuilder interface {
|
type UndirectedBuilder interface {
|
||||||
Undirected
|
Undirected
|
||||||
Builder
|
Builder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UndirectedWeightedBuilder is an undirected weighted graph builder.
|
||||||
|
type UndirectedWeightedBuilder interface {
|
||||||
|
Undirected
|
||||||
|
WeightedBuilder
|
||||||
|
}
|
||||||
|
|
||||||
// DirectedBuilder is a directed graph builder.
|
// DirectedBuilder is a directed graph builder.
|
||||||
type DirectedBuilder interface {
|
type DirectedBuilder interface {
|
||||||
Directed
|
Directed
|
||||||
Builder
|
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
|
// 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
|
// without first clearing the destination. Copy will panic if a node ID in the source
|
||||||
// graph matches a node ID in the destination.
|
// graph matches a node ID in the destination.
|
||||||
//
|
//
|
||||||
// If the source is undirected and the destination is directed both directions will
|
// If the source is undirected and the destination is directed both directions will
|
||||||
// be present in the destination after the copy is complete.
|
// 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) {
|
func Copy(dst Builder, src Graph) {
|
||||||
nodes := src.Nodes()
|
nodes := src.Nodes()
|
||||||
for _, n := range 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
|
package gen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph"
|
"gonum.org/v1/gonum/graph"
|
||||||
@@ -54,7 +53,7 @@ func (g *gnDirected) SetEdge(e graph.Edge) {
|
|||||||
func TestGnpUndirected(t *testing.T) {
|
func TestGnpUndirected(t *testing.T) {
|
||||||
for n := 2; n <= 20; n++ {
|
for n := 2; n <= 20; n++ {
|
||||||
for p := 0.; p <= 1; p += 0.1 {
|
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)
|
err := Gnp(g, n, p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err)
|
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) {
|
func TestGnpDirected(t *testing.T) {
|
||||||
for n := 2; n <= 20; n++ {
|
for n := 2; n <= 20; n++ {
|
||||||
for p := 0.; p <= 1; p += 0.1 {
|
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)
|
err := Gnp(g, n, p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err)
|
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++ {
|
for n := 2; n <= 20; n++ {
|
||||||
nChoose2 := (n - 1) * n / 2
|
nChoose2 := (n - 1) * n / 2
|
||||||
for m := 0; m <= nChoose2; m++ {
|
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)
|
err := Gnm(g, n, m, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err)
|
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++ {
|
for n := 2; n <= 20; n++ {
|
||||||
nChoose2 := (n - 1) * n / 2
|
nChoose2 := (n - 1) * n / 2
|
||||||
for m := 0; m <= nChoose2*2; m++ {
|
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)
|
err := Gnm(g, n, m, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err)
|
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 n := 2; n <= 20; n++ {
|
||||||
for d := 1; d <= (n-1)/2; d++ {
|
for d := 1; d <= (n-1)/2; d++ {
|
||||||
for p := 0.; p < 1; p += 0.1 {
|
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)
|
err := SmallWorldsBB(g, n, d, p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err)
|
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 n := 2; n <= 20; n++ {
|
||||||
for d := 1; d <= (n-1)/2; d++ {
|
for d := 1; d <= (n-1)/2; d++ {
|
||||||
for p := 0.; p < 1; p += 0.1 {
|
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)
|
err := SmallWorldsBB(g, n, d, p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err)
|
t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err)
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package gen
|
package gen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph"
|
"gonum.org/v1/gonum/graph"
|
||||||
@@ -38,7 +37,7 @@ func TestDuplication(t *testing.T) {
|
|||||||
for alpha := 0.1; alpha <= 1; alpha += 0.1 {
|
for alpha := 0.1; alpha <= 1; alpha += 0.1 {
|
||||||
for delta := 0.; delta <= 1; delta += 0.2 {
|
for delta := 0.; delta <= 1; delta += 0.2 {
|
||||||
for sigma := 0.; sigma <= 1; sigma += 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)
|
err := Duplication(g, n, delta, alpha, sigma, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, alpha=%v, delta=%v sigma=%v: %v", n, alpha, delta, sigma, err)
|
t.Fatalf("unexpected error: n=%d, alpha=%v, delta=%v sigma=%v: %v", n, alpha, delta, sigma, err)
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package gen
|
package gen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph/simple"
|
"gonum.org/v1/gonum/graph/simple"
|
||||||
@@ -15,7 +14,7 @@ func TestTunableClusteringScaleFree(t *testing.T) {
|
|||||||
for n := 2; n <= 20; n++ {
|
for n := 2; n <= 20; n++ {
|
||||||
for m := 0; m < n; m++ {
|
for m := 0; m < n; m++ {
|
||||||
for p := 0.; p <= 1; p += 0.1 {
|
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)
|
err := TunableClusteringScaleFree(g, n, m, p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, m=%d, p=%v: %v", n, m, p, err)
|
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) {
|
func TestPreferentialAttachment(t *testing.T) {
|
||||||
for n := 2; n <= 20; n++ {
|
for n := 2; n <= 20; n++ {
|
||||||
for m := 0; m < n; m++ {
|
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)
|
err := PreferentialAttachment(g, n, m, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err)
|
t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err)
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package gen
|
package gen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph/simple"
|
"gonum.org/v1/gonum/graph/simple"
|
||||||
@@ -22,7 +21,7 @@ func TestNavigableSmallWorldUndirected(t *testing.T) {
|
|||||||
for q := 0; q < 10; q++ {
|
for q := 0; q < 10; q++ {
|
||||||
for r := 0.5; r < 10; r++ {
|
for r := 0.5; r < 10; r++ {
|
||||||
for _, dims := range smallWorldDimensionParameters {
|
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)
|
err := NavigableSmallWorld(g, dims, p, q, r, nil)
|
||||||
n := 1
|
n := 1
|
||||||
for _, d := range dims {
|
for _, d := range dims {
|
||||||
@@ -51,7 +50,7 @@ func TestNavigableSmallWorldDirected(t *testing.T) {
|
|||||||
for q := 0; q < 10; q++ {
|
for q := 0; q < 10; q++ {
|
||||||
for r := 0.5; r < 10; r++ {
|
for r := 0.5; r < 10; r++ {
|
||||||
for _, dims := range smallWorldDimensionParameters {
|
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)
|
err := NavigableSmallWorld(g, dims, p, q, r, nil)
|
||||||
n := 1
|
n := 1
|
||||||
for _, d := range dims {
|
for _, d := range dims {
|
||||||
|
@@ -176,15 +176,14 @@ var betweennessTests = []struct {
|
|||||||
|
|
||||||
func TestBetweenness(t *testing.T) {
|
func TestBetweenness(t *testing.T) {
|
||||||
for i, test := range betweennessTests {
|
for i, test := range betweennessTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
for v := range e {
|
||||||
// Weight omitted to show weight-independence.
|
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
||||||
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 0})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
got := Betweenness(g)
|
got := Betweenness(g)
|
||||||
@@ -206,15 +205,14 @@ func TestBetweenness(t *testing.T) {
|
|||||||
|
|
||||||
func TestEdgeBetweenness(t *testing.T) {
|
func TestEdgeBetweenness(t *testing.T) {
|
||||||
for i, test := range betweennessTests {
|
for i, test := range betweennessTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
for v := range e {
|
||||||
// Weight omitted to show weight-independence.
|
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
||||||
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 0})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
got := EdgeBetweenness(g)
|
got := EdgeBetweenness(g)
|
||||||
@@ -239,14 +237,14 @@ func TestEdgeBetweenness(t *testing.T) {
|
|||||||
|
|
||||||
func TestBetweennessWeighted(t *testing.T) {
|
func TestBetweennessWeighted(t *testing.T) {
|
||||||
for i, test := range betweennessTests {
|
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 {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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) {
|
func TestEdgeBetweennessWeighted(t *testing.T) {
|
||||||
for i, test := range betweennessTests {
|
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 {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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))
|
prec := 1 - int(math.Log10(tol))
|
||||||
|
|
||||||
for i, test := range undirectedCentralityTests {
|
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 {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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)
|
p, ok := path.FloydWarshall(g)
|
||||||
@@ -333,14 +333,14 @@ func TestDistanceCentralityDirected(t *testing.T) {
|
|||||||
prec := 1 - int(math.Log10(tol))
|
prec := 1 - int(math.Log10(tol))
|
||||||
|
|
||||||
for i, test := range directedCentralityTests {
|
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 {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
g.AddNode(simple.Node(u))
|
g.AddNode(simple.Node(u))
|
||||||
}
|
}
|
||||||
for v := range e {
|
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)
|
p, ok := path.FloydWarshall(g)
|
||||||
|
@@ -43,7 +43,7 @@ var hitsTests = []struct {
|
|||||||
|
|
||||||
func TestHITS(t *testing.T) {
|
func TestHITS(t *testing.T) {
|
||||||
for i, test := range hitsTests {
|
for i, test := range hitsTests {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
|
@@ -81,7 +81,7 @@ var pageRankTests = []struct {
|
|||||||
|
|
||||||
func TestPageRank(t *testing.T) {
|
func TestPageRank(t *testing.T) {
|
||||||
for i, test := range pageRankTests {
|
for i, test := range pageRankTests {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -105,7 +105,7 @@ func TestPageRank(t *testing.T) {
|
|||||||
|
|
||||||
func TestPageRankSparse(t *testing.T) {
|
func TestPageRankSparse(t *testing.T) {
|
||||||
for i, test := range pageRankTests {
|
for i, test := range pageRankTests {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
|
@@ -154,7 +154,7 @@ func TestAStar(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExhaustiveAStar(t *testing.T) {
|
func TestExhaustiveAStar(t *testing.T) {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
|
||||||
nodes := []locatedNode{
|
nodes := []locatedNode{
|
||||||
{id: 1, x: 0, y: 6},
|
{id: 1, x: 0, y: 6},
|
||||||
{id: 2, x: 1, y: 0},
|
{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},
|
{from: g.Node(5), to: g.Node(6), cost: 9},
|
||||||
}
|
}
|
||||||
for _, e := range edges {
|
for _, e := range edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
heuristic := func(u, v graph.Node) float64 {
|
heuristic := func(u, v graph.Node) float64 {
|
||||||
@@ -247,7 +247,7 @@ func TestAStarNullHeuristic(t *testing.T) {
|
|||||||
for _, test := range testgraphs.ShortestPathTests {
|
for _, test := range testgraphs.ShortestPathTests {
|
||||||
g := test.Graph()
|
g := test.Graph()
|
||||||
for _, e := range test.Edges {
|
for _, e := range test.Edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -17,7 +17,7 @@ func TestBellmanFordFrom(t *testing.T) {
|
|||||||
for _, test := range testgraphs.ShortestPathTests {
|
for _, test := range testgraphs.ShortestPathTests {
|
||||||
g := test.Graph()
|
g := test.Graph()
|
||||||
for _, e := range test.Edges {
|
for _, e := range test.Edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pt, ok := BellmanFordFrom(test.Query.From(), g.(graph.Graph))
|
pt, ok := BellmanFordFrom(test.Query.From(), g.(graph.Graph))
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package path
|
package path
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph"
|
"gonum.org/v1/gonum/graph"
|
||||||
@@ -23,7 +22,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func gnpUndirected(n int, p float64) graph.Undirected {
|
func gnpUndirected(n int, p float64) graph.Undirected {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
gen.Gnp(g, n, p, nil)
|
gen.Gnp(g, n, p, nil)
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
@@ -65,7 +64,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func navigableSmallWorldUndirected(n, p, q int, r float64) graph.Undirected {
|
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)
|
gen.NavigableSmallWorld(g, []int{n, n}, p, q, r, nil)
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ func TestDijkstraFrom(t *testing.T) {
|
|||||||
for _, test := range testgraphs.ShortestPathTests {
|
for _, test := range testgraphs.ShortestPathTests {
|
||||||
g := test.Graph()
|
g := test.Graph()
|
||||||
for _, e := range test.Edges {
|
for _, e := range test.Edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -85,7 +85,7 @@ func TestDijkstraAllPaths(t *testing.T) {
|
|||||||
for _, test := range testgraphs.ShortestPathTests {
|
for _, test := range testgraphs.ShortestPathTests {
|
||||||
g := test.Graph()
|
g := test.Graph()
|
||||||
for _, e := range test.Edges {
|
for _, e := range test.Edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -33,7 +33,7 @@ type DStarLite struct {
|
|||||||
// WorldModel is a mutable weighted directed graph that returns nodes identified
|
// WorldModel is a mutable weighted directed graph that returns nodes identified
|
||||||
// by id number.
|
// by id number.
|
||||||
type WorldModel interface {
|
type WorldModel interface {
|
||||||
graph.Builder
|
graph.WeightedBuilder
|
||||||
graph.WeightedDirected
|
graph.WeightedDirected
|
||||||
Node(id int64) graph.Node
|
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 {
|
if w < 0 {
|
||||||
panic("D* Lite: negative edge weight")
|
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)
|
cOld, _ := d.model.Weight(from, to)
|
||||||
u := d.worldNodeFor(from)
|
u := d.worldNodeFor(from)
|
||||||
v := d.worldNodeFor(to)
|
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 cOld > c {
|
||||||
if u.ID() != d.t.ID() {
|
if u.ID() != d.t.ID() {
|
||||||
u.rhs = math.Min(u.rhs, c+v.g)
|
u.rhs = math.Min(u.rhs, c+v.g)
|
||||||
|
@@ -35,7 +35,7 @@ func TestDStarLiteNullHeuristic(t *testing.T) {
|
|||||||
|
|
||||||
g := test.Graph()
|
g := test.Graph()
|
||||||
for _, e := range test.Edges {
|
for _, e := range test.Edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -47,7 +47,7 @@ func TestDStarLiteNullHeuristic(t *testing.T) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
panicked = recover() != nil
|
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 panicked || test.HasNegativeWeight {
|
||||||
if !test.HasNegativeWeight {
|
if !test.HasNegativeWeight {
|
||||||
@@ -579,7 +579,7 @@ func TestDStarLiteDynamic(t *testing.T) {
|
|||||||
return test.heuristic(ax-bx, ay-by)
|
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)
|
d := NewDStarLite(test.s, test.t, l, heuristic, world)
|
||||||
var (
|
var (
|
||||||
dp *dumper
|
dp *dumper
|
||||||
|
@@ -19,7 +19,7 @@ func TestFloydWarshall(t *testing.T) {
|
|||||||
for _, test := range testgraphs.ShortestPathTests {
|
for _, test := range testgraphs.ShortestPathTests {
|
||||||
g := test.Graph()
|
g := test.Graph()
|
||||||
for _, e := range test.Edges {
|
for _, e := range test.Edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pt, ok := FloydWarshall(g.(graph.Graph))
|
pt, ok := FloydWarshall(g.(graph.Graph))
|
||||||
|
@@ -25,7 +25,7 @@ func init() {
|
|||||||
// dynamic shortest path routine in path/dynamic: DStarLite.
|
// dynamic shortest path routine in path/dynamic: DStarLite.
|
||||||
var ShortestPathTests = []struct {
|
var ShortestPathTests = []struct {
|
||||||
Name string
|
Name string
|
||||||
Graph func() graph.EdgeAdder
|
Graph func() graph.WeightedEdgeAdder
|
||||||
Edges []simple.Edge
|
Edges []simple.Edge
|
||||||
HasNegativeWeight bool
|
HasNegativeWeight bool
|
||||||
HasNegativeCycle bool
|
HasNegativeCycle bool
|
||||||
@@ -40,7 +40,7 @@ var ShortestPathTests = []struct {
|
|||||||
// Positive weighted graphs.
|
// Positive weighted graphs.
|
||||||
{
|
{
|
||||||
Name: "empty directed",
|
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)},
|
Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
|
||||||
Weight: math.Inf(1),
|
Weight: math.Inf(1),
|
||||||
@@ -49,7 +49,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "empty undirected",
|
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)},
|
Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
|
||||||
Weight: math.Inf(1),
|
Weight: math.Inf(1),
|
||||||
@@ -58,7 +58,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "one edge directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
},
|
},
|
||||||
@@ -74,7 +74,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "one edge self directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
},
|
},
|
||||||
@@ -90,7 +90,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "one edge undirected",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
},
|
},
|
||||||
@@ -106,7 +106,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "two paths directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(2), W: 2},
|
{F: simple.Node(0), T: simple.Node(2), W: 2},
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -125,7 +125,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "two paths undirected",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(2), W: 2},
|
{F: simple.Node(0), T: simple.Node(2), W: 2},
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -144,7 +144,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "confounding paths directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->5 of weight 4
|
// Add a path from 0->5 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -178,7 +178,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "confounding paths undirected",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->5 of weight 4
|
// Add a path from 0->5 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -212,7 +212,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "confounding paths directed 2-step",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->5 of weight 4
|
// Add a path from 0->5 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -247,7 +247,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "confounding paths undirected 2-step",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->5 of weight 4
|
// Add a path from 0->5 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -282,7 +282,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "zero-weight cycle directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -306,7 +306,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "zero-weight cycle^2 directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -333,7 +333,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "zero-weight cycle^2 confounding directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
||||||
@@ -363,7 +363,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "zero-weight cycle^3 directed",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{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",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{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",
|
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{
|
Edges: []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 1},
|
{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",
|
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 {
|
Edges: func() []simple.Edge {
|
||||||
e := []simple.Edge{
|
e := []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
@@ -498,7 +498,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "zero-weight n·cycle directed",
|
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 {
|
Edges: func() []simple.Edge {
|
||||||
e := []simple.Edge{
|
e := []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// 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",
|
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 {
|
Edges: func() []simple.Edge {
|
||||||
e := []simple.Edge{
|
e := []simple.Edge{
|
||||||
// Add a path from 0->4 of weight 4
|
// Add a path from 0->4 of weight 4
|
||||||
@@ -579,7 +579,7 @@ var ShortestPathTests = []struct {
|
|||||||
// Negative weighted graphs.
|
// Negative weighted graphs.
|
||||||
{
|
{
|
||||||
Name: "one edge directed negative",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: -1},
|
{F: simple.Node(0), T: simple.Node(1), W: -1},
|
||||||
},
|
},
|
||||||
@@ -596,7 +596,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "one edge undirected negative",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: -1},
|
{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
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node('w'), T: simple.Node('z'), W: 2},
|
{F: simple.Node('w'), T: simple.Node('z'), W: 2},
|
||||||
{F: simple.Node('x'), T: simple.Node('w'), W: 6},
|
{F: simple.Node('x'), T: simple.Node('w'), W: 6},
|
||||||
@@ -630,7 +630,7 @@ var ShortestPathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "roughgarden negative",
|
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{
|
Edges: []simple.Edge{
|
||||||
{F: simple.Node('a'), T: simple.Node('b'), W: -2},
|
{F: simple.Node('a'), T: simple.Node('b'), W: -2},
|
||||||
{F: simple.Node('b'), T: simple.Node('c'), W: -1},
|
{F: simple.Node('b'), T: simple.Node('c'), W: -1},
|
||||||
|
@@ -19,7 +19,7 @@ func TestJohnsonAllPaths(t *testing.T) {
|
|||||||
for _, test := range testgraphs.ShortestPathTests {
|
for _, test := range testgraphs.ShortestPathTests {
|
||||||
g := test.Graph()
|
g := test.Graph()
|
||||||
for _, e := range test.Edges {
|
for _, e := range test.Edges {
|
||||||
g.SetEdge(e)
|
g.SetWeightedEdge(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pt, ok := JohnsonAllPaths(g.(graph.Graph))
|
pt, ok := JohnsonAllPaths(g.(graph.Graph))
|
||||||
|
@@ -26,7 +26,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type spanningGraph interface {
|
type spanningGraph interface {
|
||||||
graph.Builder
|
graph.WeightedBuilder
|
||||||
graph.WeightedUndirected
|
graph.WeightedUndirected
|
||||||
Edges() []graph.Edge
|
Edges() []graph.Edge
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ var spanningTreeTests = []struct {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Empty",
|
name: "Empty",
|
||||||
graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) },
|
graph: func() spanningGraph { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
|
||||||
want: 0,
|
want: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -48,7 +48,7 @@ var spanningTreeTests = []struct {
|
|||||||
// Modified to make edge weights unique; A--B is increased to 2.5 otherwise
|
// Modified to make edge weights unique; A--B is increased to 2.5 otherwise
|
||||||
// to prevent the alternative solution being found.
|
// to prevent the alternative solution being found.
|
||||||
name: "Prim WP figure 1",
|
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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node('A'), T: simple.Node('B'), W: 2.5},
|
{F: simple.Node('A'), T: simple.Node('B'), W: 2.5},
|
||||||
{F: simple.Node('A'), T: simple.Node('D'), W: 1},
|
{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
|
// https://upload.wikimedia.org/wikipedia/commons/5/5c/MST_kruskal_en.gif
|
||||||
name: "Kruskal WP figure 1",
|
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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node('a'), T: simple.Node('b'), W: 3},
|
{F: simple.Node('a'), T: simple.Node('b'), W: 3},
|
||||||
{F: simple.Node('a'), T: simple.Node('e'), W: 1},
|
{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
|
// https://upload.wikimedia.org/wikipedia/commons/8/87/Kruskal_Algorithm_6.svg
|
||||||
name: "Kruskal WP example",
|
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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node('A'), T: simple.Node('B'), W: 7},
|
{F: simple.Node('A'), T: simple.Node('B'), W: 7},
|
||||||
{F: simple.Node('A'), T: simple.Node('D'), W: 5},
|
{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
|
// https://upload.wikimedia.org/wikipedia/commons/2/2e/Boruvka%27s_algorithm_%28Sollin%27s_algorithm%29_Anim.gif
|
||||||
name: "Borůvka WP example",
|
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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node('A'), T: simple.Node('B'), W: 13},
|
{F: simple.Node('A'), T: simple.Node('B'), W: 13},
|
||||||
{F: simple.Node('A'), T: simple.Node('C'), W: 6},
|
{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
|
// https://upload.wikimedia.org/wikipedia/commons/d/d2/Minimum_spanning_tree.svg
|
||||||
// Nodes labelled row major.
|
// Nodes labelled row major.
|
||||||
name: "Minimum Spanning Tree WP figure 1",
|
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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node(1), T: simple.Node(2), W: 4},
|
{F: simple.Node(1), T: simple.Node(2), W: 4},
|
||||||
{F: simple.Node(1), T: simple.Node(3), W: 1},
|
{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
|
// 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.
|
// but with C--H and E--J cut.
|
||||||
name: "Borůvka WP example 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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node('A'), T: simple.Node('B'), W: 13},
|
{F: simple.Node('A'), T: simple.Node('B'), W: 13},
|
||||||
{F: simple.Node('A'), T: simple.Node('C'), W: 6},
|
{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 {
|
for _, test := range spanningTreeTests {
|
||||||
g := test.graph()
|
g := test.graph()
|
||||||
for _, e := range test.edges {
|
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)
|
w := mst(dst, g)
|
||||||
if w != test.want {
|
if w != test.want {
|
||||||
t.Errorf("unexpected minimum spanning tree weight for %q: got: %f want: %f",
|
t.Errorf("unexpected minimum spanning tree weight for %q: got: %f want: %f",
|
||||||
test.name, w, test.want)
|
test.name, w, test.want)
|
||||||
}
|
}
|
||||||
var got float64
|
var got float64
|
||||||
for _, e := range dst.Edges() {
|
for _, e := range dst.WeightedEdges() {
|
||||||
got += e.Weight()
|
got += e.Weight()
|
||||||
}
|
}
|
||||||
if got != test.want {
|
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) {
|
func TestKruskal(t *testing.T) {
|
||||||
testMinumumSpanning(func(dst graph.UndirectedBuilder, g spanningGraph) float64 {
|
testMinumumSpanning(func(dst graph.UndirectedBuilder, g spanningGraph) float64 {
|
||||||
return Kruskal(dst, g)
|
return Kruskal(dst, g)
|
||||||
|
@@ -218,9 +218,19 @@ func (g *DirectedMatrix) Weight(x, y graph.Node) (w float64, ok bool) {
|
|||||||
return g.absent, false
|
return g.absent, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEdge sets e, an edge from one node to another. If the ends of the edge are not in g
|
// SetEdge sets e, an edge from one node to another with unit weight. If the ends of the edge
|
||||||
// or the edge is a self loop, SetEdge panics.
|
// are not in g or the edge is a self loop, SetEdge panics.
|
||||||
func (g *DirectedMatrix) SetEdge(e graph.Edge) {
|
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()
|
fid := e.From().ID()
|
||||||
tid := e.To().ID()
|
tid := e.To().ID()
|
||||||
if fid == tid {
|
if fid == tid {
|
||||||
@@ -233,7 +243,7 @@ func (g *DirectedMatrix) SetEdge(e graph.Edge) {
|
|||||||
panic("simple: unavailable to node ID for dense graph")
|
panic("simple: unavailable to node ID for dense graph")
|
||||||
}
|
}
|
||||||
// fid and tid are not greater than maximum int by this point.
|
// 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
|
// 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
|
return g.absent, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEdge sets e, an edge from one node to another. If the ends of the edge are not in g
|
// SetEdge sets e, an edge from one node to another with unit weight. If the ends of the edge are
|
||||||
// or the edge is a self loop, SetEdge panics.
|
// not in g or the edge is a self loop, SetEdge panics.
|
||||||
func (g *UndirectedMatrix) SetEdge(e graph.Edge) {
|
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()
|
fid := e.From().ID()
|
||||||
tid := e.To().ID()
|
tid := e.To().ID()
|
||||||
if fid == tid {
|
if fid == tid {
|
||||||
@@ -205,7 +215,7 @@ func (g *UndirectedMatrix) SetEdge(e graph.Edge) {
|
|||||||
panic("simple: unavailable to node ID for dense graph")
|
panic("simple: unavailable to node ID for dense graph")
|
||||||
}
|
}
|
||||||
// fid and tid are not greater than maximum int by this point.
|
// 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
|
// 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
|
from map[int64]map[int64]graph.Edge
|
||||||
to map[int64]map[int64]graph.Edge
|
to map[int64]map[int64]graph.Edge
|
||||||
|
|
||||||
self, absent float64
|
|
||||||
|
|
||||||
nodeIDs idSet
|
nodeIDs idSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDirectedGraph returns a DirectedGraph with the specified self and absent
|
// NewDirectedGraph returns a DirectedGraph with the specified self and absent
|
||||||
// edge weight values.
|
// edge weight values.
|
||||||
func NewDirectedGraph(self, absent float64) *DirectedGraph {
|
func NewDirectedGraph() *DirectedGraph {
|
||||||
return &DirectedGraph{
|
return &DirectedGraph{
|
||||||
nodes: make(map[int64]graph.Node),
|
nodes: make(map[int64]graph.Node),
|
||||||
from: make(map[int64]map[int64]graph.Edge),
|
from: make(map[int64]map[int64]graph.Edge),
|
||||||
to: make(map[int64]map[int64]graph.Edge),
|
to: make(map[int64]map[int64]graph.Edge),
|
||||||
|
|
||||||
self: self,
|
|
||||||
absent: absent,
|
|
||||||
|
|
||||||
nodeIDs: newIDSet(),
|
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.
|
// 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.
|
// 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 {
|
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 {
|
if _, ok := g.nodes[u.ID()]; !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -246,24 +235,6 @@ func (g *DirectedGraph) HasEdgeFromTo(u, v graph.Node) bool {
|
|||||||
return true
|
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.
|
// Degree returns the in+out degree of n in g.
|
||||||
func (g *DirectedGraph) Degree(n graph.Node) int {
|
func (g *DirectedGraph) Degree(n graph.Node) int {
|
||||||
if _, ok := g.nodes[n.ID()]; !ok {
|
if _, ok := g.nodes[n.ID()]; !ok {
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package simple
|
package simple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph"
|
"gonum.org/v1/gonum/graph"
|
||||||
@@ -14,9 +13,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
directedGraph = (*DirectedGraph)(nil)
|
directedGraph = (*DirectedGraph)(nil)
|
||||||
|
|
||||||
_ graph.Graph = directedGraph
|
_ graph.Graph = directedGraph
|
||||||
_ graph.Directed = directedGraph
|
_ graph.Directed = directedGraph
|
||||||
_ graph.WeightedDirected = directedGraph
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests Issue #27
|
// Tests Issue #27
|
||||||
@@ -36,10 +34,10 @@ func generateDummyGraph() *DirectedGraph {
|
|||||||
{0, 2},
|
{0, 2},
|
||||||
}
|
}
|
||||||
|
|
||||||
g := NewDirectedGraph(0, math.Inf(1))
|
g := NewDirectedGraph()
|
||||||
|
|
||||||
for _, n := range nodes {
|
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
|
return g
|
||||||
@@ -52,7 +50,7 @@ func TestIssue123DirectedGraph(t *testing.T) {
|
|||||||
t.Errorf("unexpected panic: %v", r)
|
t.Errorf("unexpected panic: %v", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
g := NewDirectedGraph(0, math.Inf(1))
|
g := NewDirectedGraph()
|
||||||
|
|
||||||
n0 := g.NewNode()
|
n0 := g.NewNode()
|
||||||
g.AddNode(n0)
|
g.AddNode(n0)
|
||||||
|
@@ -15,21 +15,16 @@ type UndirectedGraph struct {
|
|||||||
nodes map[int64]graph.Node
|
nodes map[int64]graph.Node
|
||||||
edges map[int64]map[int64]graph.Edge
|
edges map[int64]map[int64]graph.Edge
|
||||||
|
|
||||||
self, absent float64
|
|
||||||
|
|
||||||
nodeIDs idSet
|
nodeIDs idSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUndirectedGraph returns an UndirectedGraph with the specified self and absent
|
// NewUndirectedGraph returns an UndirectedGraph with the specified self and absent
|
||||||
// edge weight values.
|
// edge weight values.
|
||||||
func NewUndirectedGraph(self, absent float64) *UndirectedGraph {
|
func NewUndirectedGraph() *UndirectedGraph {
|
||||||
return &UndirectedGraph{
|
return &UndirectedGraph{
|
||||||
nodes: make(map[int64]graph.Node),
|
nodes: make(map[int64]graph.Node),
|
||||||
edges: make(map[int64]map[int64]graph.Edge),
|
edges: make(map[int64]map[int64]graph.Edge),
|
||||||
|
|
||||||
self: self,
|
|
||||||
absent: absent,
|
|
||||||
|
|
||||||
nodeIDs: newIDSet(),
|
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.
|
// 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.
|
// 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 {
|
func (g *UndirectedGraph) Edge(u, v graph.Node) graph.Edge {
|
||||||
return g.WeightedEdgeBetween(u, v)
|
return g.EdgeBetween(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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EdgeBetween returns the edge between nodes x and y.
|
// EdgeBetween returns the edge between nodes x and y.
|
||||||
func (g *UndirectedGraph) EdgeBetween(x, y graph.Node) graph.Edge {
|
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
|
// We don't need to check if neigh exists because
|
||||||
// it's implicit in the edges access.
|
// it's implicit in the edges access.
|
||||||
if !g.Has(x) {
|
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()]
|
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.
|
// Degree returns the degree of n in g.
|
||||||
func (g *UndirectedGraph) Degree(n graph.Node) int {
|
func (g *UndirectedGraph) Degree(n graph.Node) int {
|
||||||
if _, ok := g.nodes[n.ID()]; !ok {
|
if _, ok := g.nodes[n.ID()]; !ok {
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package simple
|
package simple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph"
|
"gonum.org/v1/gonum/graph"
|
||||||
@@ -14,20 +13,19 @@ import (
|
|||||||
var (
|
var (
|
||||||
undirectedGraph = (*UndirectedGraph)(nil)
|
undirectedGraph = (*UndirectedGraph)(nil)
|
||||||
|
|
||||||
_ graph.Graph = undirectedGraph
|
_ graph.Graph = undirectedGraph
|
||||||
_ graph.Undirected = undirectedGraph
|
_ graph.Undirected = undirectedGraph
|
||||||
_ graph.WeightedUndirected = undirectedGraph
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAssertMutableNotDirected(t *testing.T) {
|
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 {
|
if _, ok := g.(graph.Directed); ok {
|
||||||
t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!")
|
t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaxID(t *testing.T) {
|
func TestMaxID(t *testing.T) {
|
||||||
g := NewUndirectedGraph(0, math.Inf(1))
|
g := NewUndirectedGraph()
|
||||||
nodes := make(map[graph.Node]struct{})
|
nodes := make(map[graph.Node]struct{})
|
||||||
for i := Node(0); i < 3; i++ {
|
for i := Node(0); i < 3; i++ {
|
||||||
g.AddNode(i)
|
g.AddNode(i)
|
||||||
@@ -54,7 +52,7 @@ func TestIssue123UndirectedGraph(t *testing.T) {
|
|||||||
t.Errorf("unexpected panic: %v", r)
|
t.Errorf("unexpected panic: %v", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
g := NewUndirectedGraph(0, math.Inf(1))
|
g := NewUndirectedGraph()
|
||||||
|
|
||||||
n0 := g.NewNode()
|
n0 := g.NewNode()
|
||||||
g.AddNode(n0)
|
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
|
package topo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/graph"
|
"gonum.org/v1/gonum/graph"
|
||||||
@@ -23,7 +22,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func gnpDirected(n int, p float64) graph.Directed {
|
func gnpDirected(n int, p float64) graph.Directed {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
gen.Gnp(g, n, p, nil)
|
gen.Gnp(g, n, p, nil)
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package topo
|
package topo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -51,7 +50,7 @@ var vOrderTests = []struct {
|
|||||||
|
|
||||||
func TestVertexOrdering(t *testing.T) {
|
func TestVertexOrdering(t *testing.T) {
|
||||||
for i, test := range vOrderTests {
|
for i, test := range vOrderTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -136,7 +135,7 @@ var bronKerboschTests = []struct {
|
|||||||
|
|
||||||
func TestBronKerbosch(t *testing.T) {
|
func TestBronKerbosch(t *testing.T) {
|
||||||
for i, test := range bronKerboschTests {
|
for i, test := range bronKerboschTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package topo
|
package topo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -85,7 +84,7 @@ var cyclesInTests = []struct {
|
|||||||
|
|
||||||
func TestDirectedCyclesIn(t *testing.T) {
|
func TestDirectedCyclesIn(t *testing.T) {
|
||||||
for i, test := range cyclesInTests {
|
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.
|
g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs.
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package topo
|
package topo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -74,7 +73,7 @@ var undirectedCyclesInTests = []struct {
|
|||||||
|
|
||||||
func TestUndirectedCyclesIn(t *testing.T) {
|
func TestUndirectedCyclesIn(t *testing.T) {
|
||||||
for i, test := range undirectedCyclesInTests {
|
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.
|
g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs.
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package topo
|
package topo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -130,7 +129,7 @@ var tarjanTests = []struct {
|
|||||||
|
|
||||||
func TestSort(t *testing.T) {
|
func TestSort(t *testing.T) {
|
||||||
for i, test := range tarjanTests {
|
for i, test := range tarjanTests {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -161,7 +160,7 @@ func TestSort(t *testing.T) {
|
|||||||
|
|
||||||
func TestTarjanSCC(t *testing.T) {
|
func TestTarjanSCC(t *testing.T) {
|
||||||
for i, test := range tarjanTests {
|
for i, test := range tarjanTests {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -288,7 +287,7 @@ var stabilizedSortTests = []struct {
|
|||||||
|
|
||||||
func TestSortStabilized(t *testing.T) {
|
func TestSortStabilized(t *testing.T) {
|
||||||
for i, test := range stabilizedSortTests {
|
for i, test := range stabilizedSortTests {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
package topo
|
package topo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestIsPath(t *testing.T) {
|
func TestIsPath(t *testing.T) {
|
||||||
dg := simple.NewDirectedGraph(0, math.Inf(1))
|
dg := simple.NewDirectedGraph()
|
||||||
if !IsPathIn(dg, nil) {
|
if !IsPathIn(dg, nil) {
|
||||||
t.Error("IsPath returns false on nil path")
|
t.Error("IsPath returns false on nil path")
|
||||||
}
|
}
|
||||||
@@ -33,7 +32,7 @@ func TestIsPath(t *testing.T) {
|
|||||||
if IsPathIn(dg, p) {
|
if IsPathIn(dg, p) {
|
||||||
t.Error("IsPath returns true on bad path of length 2")
|
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) {
|
if !IsPathIn(dg, p) {
|
||||||
t.Error("IsPath returns false on correct path of length 2")
|
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")
|
t.Error("IsPath erroneously returns true for a reverse path")
|
||||||
}
|
}
|
||||||
p = []graph.Node{p[1], p[0], simple.Node(2)}
|
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) {
|
if !IsPathIn(dg, p) {
|
||||||
t.Error("IsPath does not find a correct path for path > 2 nodes")
|
t.Error("IsPath does not find a correct path for path > 2 nodes")
|
||||||
}
|
}
|
||||||
ug := simple.NewUndirectedGraph(0, math.Inf(1))
|
ug := simple.NewUndirectedGraph()
|
||||||
ug.SetEdge(simple.Edge{F: p[1], T: p[0], W: 1})
|
ug.SetEdge(simple.Edge{F: p[1], T: p[0]})
|
||||||
ug.SetEdge(simple.Edge{F: p[1], T: p[2], W: 1})
|
ug.SetEdge(simple.Edge{F: p[1], T: p[2]})
|
||||||
if !IsPathIn(dg, p) {
|
if !IsPathIn(dg, p) {
|
||||||
t.Error("IsPath does not correctly account for undirected behavior")
|
t.Error("IsPath does not correctly account for undirected behavior")
|
||||||
}
|
}
|
||||||
@@ -69,7 +68,7 @@ var pathExistsInUndirectedTests = []struct {
|
|||||||
|
|
||||||
func TestPathExistsInUndirected(t *testing.T) {
|
func TestPathExistsInUndirected(t *testing.T) {
|
||||||
for i, test := range pathExistsInUndirectedTests {
|
for i, test := range pathExistsInUndirectedTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
|
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -108,7 +107,7 @@ var pathExistsInDirectedTests = []struct {
|
|||||||
|
|
||||||
func TestPathExistsInDirected(t *testing.T) {
|
func TestPathExistsInDirected(t *testing.T) {
|
||||||
for i, test := range pathExistsInDirectedTests {
|
for i, test := range pathExistsInDirectedTests {
|
||||||
g := simple.NewDirectedGraph(0, math.Inf(1))
|
g := simple.NewDirectedGraph()
|
||||||
|
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -145,7 +144,7 @@ var connectedComponentTests = []struct {
|
|||||||
|
|
||||||
func TestConnectedComponents(t *testing.T) {
|
func TestConnectedComponents(t *testing.T) {
|
||||||
for i, test := range connectedComponentTests {
|
for i, test := range connectedComponentTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
|
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
|
@@ -6,7 +6,6 @@ package traverse
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -134,7 +133,7 @@ var breadthFirstTests = []struct {
|
|||||||
|
|
||||||
func TestBreadthFirst(t *testing.T) {
|
func TestBreadthFirst(t *testing.T) {
|
||||||
for i, test := range breadthFirstTests {
|
for i, test := range breadthFirstTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -222,7 +221,7 @@ var depthFirstTests = []struct {
|
|||||||
|
|
||||||
func TestDepthFirst(t *testing.T) {
|
func TestDepthFirst(t *testing.T) {
|
||||||
for i, test := range depthFirstTests {
|
for i, test := range depthFirstTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
// Add nodes that are not defined by an edge.
|
// Add nodes that are not defined by an edge.
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -283,7 +282,7 @@ var walkAllTests = []struct {
|
|||||||
|
|
||||||
func TestWalkAll(t *testing.T) {
|
func TestWalkAll(t *testing.T) {
|
||||||
for i, test := range walkAllTests {
|
for i, test := range walkAllTests {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
|
|
||||||
for u, e := range test.g {
|
for u, e := range test.g {
|
||||||
if !g.Has(simple.Node(u)) {
|
if !g.Has(simple.Node(u)) {
|
||||||
@@ -366,7 +365,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func gnpUndirected(n int, p float64) graph.Undirected {
|
func gnpUndirected(n int, p float64) graph.Undirected {
|
||||||
g := simple.NewUndirectedGraph(0, math.Inf(1))
|
g := simple.NewUndirectedGraph()
|
||||||
gen.Gnp(g, n, p, nil)
|
gen.Gnp(g, n, p, nil)
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
@@ -4,36 +4,12 @@
|
|||||||
|
|
||||||
package graph
|
package graph
|
||||||
|
|
||||||
// Undirect converts a directed graph to an undirected graph, resolving
|
// Undirect converts a directed graph to an undirected graph.
|
||||||
// edge weight conflicts.
|
|
||||||
type Undirect struct {
|
type Undirect struct {
|
||||||
G Directed
|
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 (
|
var _ Undirected = Undirect{}
|
||||||
_ Undirected = Undirect{}
|
|
||||||
_ WeightedUndirected = Undirect{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Has returns whether the node exists within the graph.
|
// Has returns whether the node exists within the graph.
|
||||||
func (g Undirect) Has(n Node) bool { return g.G.Has(n) }
|
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
|
// 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
|
// the edge is determined by applying the Merge func to the weights of the
|
||||||
// edges between u and v.
|
// edges between u and v.
|
||||||
func (g Undirect) Edge(u, v Node) Edge { return g.WeightedEdgeBetween(u, v) }
|
func (g Undirect) Edge(u, v Node) Edge { return g.EdgeBetween(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) }
|
|
||||||
|
|
||||||
// EdgeBetween returns the edge between nodes x and y. If an edge exists, the
|
// 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
|
// 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.
|
// applying the Merge func to the weights of edges between x and y.
|
||||||
func (g Undirect) EdgeBetween(x, y Node) Edge {
|
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)
|
fe := g.G.Edge(x, y)
|
||||||
re := g.G.Edge(y, x)
|
re := g.G.Edge(y, x)
|
||||||
if fe == nil && re == nil {
|
if fe == nil && re == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var f, r float64
|
return EdgePair{fe, re}
|
||||||
if wg, ok := g.G.(WeightedDirected); ok {
|
}
|
||||||
f, ok = wg.Weight(x, y)
|
|
||||||
if !ok {
|
// UndirectWeighted converts a directed weighted graph to an undirected weighted graph,
|
||||||
f = g.Absent
|
// 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)
|
seen[n.ID()] = struct{}{}
|
||||||
if !ok {
|
nodes = append(nodes, n)
|
||||||
r = g.Absent
|
}
|
||||||
}
|
return nodes
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// 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
|
f = g.Absent
|
||||||
if fe != nil {
|
}
|
||||||
f = fe.Weight()
|
r, ok := g.G.Weight(y, x)
|
||||||
}
|
if !ok {
|
||||||
r = g.Absent
|
r = g.Absent
|
||||||
if re != nil {
|
|
||||||
r = re.Weight()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var w float64
|
var w float64
|
||||||
@@ -121,41 +164,26 @@ func (g Undirect) WeightedEdgeBetween(x, y Node) WeightedEdge {
|
|||||||
} else {
|
} else {
|
||||||
w = g.Merge(f, r, fe, re)
|
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.
|
// 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
|
// 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
|
// 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.
|
// 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)
|
fe := g.G.Edge(x, y)
|
||||||
re := g.G.Edge(y, x)
|
re := g.G.Edge(y, x)
|
||||||
|
|
||||||
var f, r float64
|
f, fOk := g.G.Weight(x, y)
|
||||||
if wg, wOk := g.G.(WeightedDirected); wOk {
|
if !fOk {
|
||||||
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 = g.Absent
|
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 {
|
if g.Merge == nil {
|
||||||
return (f + r) / 2, ok
|
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.
|
// EdgePair is an opposed pair of directed edges.
|
||||||
type EdgePair struct {
|
type EdgePair [2]Edge
|
||||||
E [2]Edge
|
|
||||||
W float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// From returns the from node of the first non-nil edge, or nil.
|
// From returns the from node of the first non-nil edge, or nil.
|
||||||
func (e EdgePair) From() Node {
|
func (e EdgePair) From() Node {
|
||||||
if e.E[0] != nil {
|
if e[0] != nil {
|
||||||
return e.E[0].From()
|
return e[0].From()
|
||||||
} else if e.E[1] != nil {
|
} else if e[1] != nil {
|
||||||
return e.E[1].From()
|
return e[1].From()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// To returns the to node of the first non-nil edge, or nil.
|
// To returns the to node of the first non-nil edge, or nil.
|
||||||
func (e EdgePair) To() Node {
|
func (e EdgePair) To() Node {
|
||||||
if e.E[0] != nil {
|
if e[0] != nil {
|
||||||
return e.E[0].To()
|
return e[0].To()
|
||||||
} else if e.E[1] != nil {
|
} else if e[1] != nil {
|
||||||
return e.E[1].To()
|
return e[1].To()
|
||||||
}
|
}
|
||||||
return nil
|
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.
|
// 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"
|
"gonum.org/v1/gonum/mat"
|
||||||
)
|
)
|
||||||
|
|
||||||
var directedGraphs = []struct {
|
type weightedDirectedBuilder interface {
|
||||||
g func() graph.DirectedBuilder
|
graph.WeightedBuilder
|
||||||
|
graph.WeightedDirected
|
||||||
|
}
|
||||||
|
|
||||||
|
var weightedDirectedGraphs = []struct {
|
||||||
|
skipUnweighted bool
|
||||||
|
|
||||||
|
g func() weightedDirectedBuilder
|
||||||
edges []simple.Edge
|
edges []simple.Edge
|
||||||
absent float64
|
absent float64
|
||||||
merge func(x, y float64, xe, ye graph.Edge) float64
|
merge func(x, y float64, xe, ye graph.Edge) float64
|
||||||
@@ -22,7 +29,7 @@ var directedGraphs = []struct {
|
|||||||
want mat.Matrix
|
want mat.Matrix
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) },
|
g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) },
|
||||||
edges: []simple.Edge{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
||||||
{F: simple.Node(1), T: simple.Node(0), W: 1},
|
{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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
||||||
{F: simple.Node(1), T: simple.Node(0), W: 1},
|
{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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
||||||
{F: simple.Node(1), T: simple.Node(0), W: 1},
|
{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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
||||||
{F: simple.Node(1), T: simple.Node(0), W: 1},
|
{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{
|
edges: []simple.Edge{
|
||||||
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
{F: simple.Node(0), T: simple.Node(1), W: 2},
|
||||||
{F: simple.Node(1), T: simple.Node(0), W: 1},
|
{F: simple.Node(1), T: simple.Node(0), W: 1},
|
||||||
@@ -102,13 +111,16 @@ var directedGraphs = []struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUndirect(t *testing.T) {
|
func TestUndirect(t *testing.T) {
|
||||||
for _, test := range directedGraphs {
|
for i, test := range weightedDirectedGraphs {
|
||||||
|
if test.skipUnweighted {
|
||||||
|
continue
|
||||||
|
}
|
||||||
g := test.g()
|
g := test.g()
|
||||||
for _, e := range test.edges {
|
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)
|
dst := simple.NewUndirectedMatrixFrom(src.Nodes(), 0, 0, 0)
|
||||||
for _, u := range src.Nodes() {
|
for _, u := range src.Nodes() {
|
||||||
for _, v := range src.From(u) {
|
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) {
|
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(dst.Matrix()),
|
||||||
mat.Formatted(test.want),
|
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