blob: 19c62cec6fd7854951a2000bc538b840a21b1601 [file] [log] [blame] [edit]
// Copyright ©2019 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 layout_test
import (
"path/filepath"
"testing"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/spatial/r2"
"gonum.org/v1/plot"
"gonum.org/v1/plot/vg"
. "gonum.org/v1/gonum/graph/layout"
)
func TestEadesR2(t *testing.T) {
eadesR2Tests := []struct {
name string
g graph.Graph
param EadesR2
wantIters int
}{
{
name: "line",
g: func() graph.Graph {
edges := []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return orderedGraph{g}
}(),
param: EadesR2{Repulsion: 1, Rate: 0.1, Updates: 100, Theta: 0.1, Src: rand.NewSource(1)},
wantIters: 100,
},
{
name: "square",
g: func() graph.Graph {
edges := []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(0), T: simple.Node(2)},
{F: simple.Node(1), T: simple.Node(3)},
{F: simple.Node(2), T: simple.Node(3)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return orderedGraph{g}
}(),
param: EadesR2{Repulsion: 1, Rate: 0.1, Updates: 100, Theta: 0.1, Src: rand.NewSource(1)},
wantIters: 100,
},
{
name: "tetrahedron",
g: func() graph.Graph {
edges := []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(0), T: simple.Node(2)},
{F: simple.Node(0), T: simple.Node(3)},
{F: simple.Node(1), T: simple.Node(2)},
{F: simple.Node(1), T: simple.Node(3)},
{F: simple.Node(2), T: simple.Node(3)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return orderedGraph{g}
}(),
param: EadesR2{Repulsion: 1, Rate: 0.1, Updates: 100, Theta: 0.1, Src: rand.NewSource(1)},
wantIters: 100,
},
{
name: "sheet",
g: func() graph.Graph {
edges := []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(0), T: simple.Node(3)},
{F: simple.Node(1), T: simple.Node(2)},
{F: simple.Node(1), T: simple.Node(4)},
{F: simple.Node(2), T: simple.Node(5)},
{F: simple.Node(3), T: simple.Node(4)},
{F: simple.Node(3), T: simple.Node(6)},
{F: simple.Node(4), T: simple.Node(5)},
{F: simple.Node(4), T: simple.Node(7)},
{F: simple.Node(5), T: simple.Node(8)},
{F: simple.Node(6), T: simple.Node(7)},
{F: simple.Node(7), T: simple.Node(8)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return orderedGraph{g}
}(),
param: EadesR2{Repulsion: 1, Rate: 0.1, Updates: 100, Theta: 0.1, Src: rand.NewSource(1)},
wantIters: 100,
},
{
name: "tube",
g: func() graph.Graph {
edges := []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(0), T: simple.Node(2)},
{F: simple.Node(0), T: simple.Node(3)},
{F: simple.Node(1), T: simple.Node(2)},
{F: simple.Node(1), T: simple.Node(4)},
{F: simple.Node(2), T: simple.Node(5)},
{F: simple.Node(3), T: simple.Node(4)},
{F: simple.Node(3), T: simple.Node(5)},
{F: simple.Node(3), T: simple.Node(6)},
{F: simple.Node(4), T: simple.Node(5)},
{F: simple.Node(4), T: simple.Node(7)},
{F: simple.Node(5), T: simple.Node(8)},
{F: simple.Node(6), T: simple.Node(7)},
{F: simple.Node(6), T: simple.Node(8)},
{F: simple.Node(7), T: simple.Node(8)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return orderedGraph{g}
}(),
param: EadesR2{Repulsion: 1, Rate: 0.1, Updates: 100, Theta: 0.1, Src: rand.NewSource(1)},
wantIters: 100,
},
{
// This test does not produce a good layout, but is here to
// ensure that Update does not panic with steep decent rates.
name: "tube-steep",
g: func() graph.Graph {
edges := []simple.Edge{
{F: simple.Node(0), T: simple.Node(1)},
{F: simple.Node(0), T: simple.Node(2)},
{F: simple.Node(0), T: simple.Node(3)},
{F: simple.Node(1), T: simple.Node(2)},
{F: simple.Node(1), T: simple.Node(4)},
{F: simple.Node(2), T: simple.Node(5)},
{F: simple.Node(3), T: simple.Node(4)},
{F: simple.Node(3), T: simple.Node(5)},
{F: simple.Node(3), T: simple.Node(6)},
{F: simple.Node(4), T: simple.Node(5)},
{F: simple.Node(4), T: simple.Node(7)},
{F: simple.Node(5), T: simple.Node(8)},
{F: simple.Node(6), T: simple.Node(7)},
{F: simple.Node(6), T: simple.Node(8)},
{F: simple.Node(7), T: simple.Node(8)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return orderedGraph{g}
}(),
param: EadesR2{Repulsion: 1, Rate: 1, Updates: 100, Theta: 0.1, Src: rand.NewSource(1)},
wantIters: 99,
},
{
name: "wp_page", // https://en.wikipedia.org/wiki/PageRank#/media/File:PageRanks-Example.jpg
g: func() graph.Graph {
edges := []simple.Edge{
{F: simple.Node(0), T: simple.Node(3)},
{F: simple.Node(1), T: simple.Node(2)},
{F: simple.Node(1), T: simple.Node(3)},
{F: simple.Node(1), T: simple.Node(4)},
{F: simple.Node(1), T: simple.Node(5)},
{F: simple.Node(1), T: simple.Node(6)},
{F: simple.Node(1), T: simple.Node(7)},
{F: simple.Node(1), T: simple.Node(8)},
{F: simple.Node(3), T: simple.Node(4)},
{F: simple.Node(4), T: simple.Node(5)},
{F: simple.Node(4), T: simple.Node(6)},
{F: simple.Node(4), T: simple.Node(7)},
{F: simple.Node(4), T: simple.Node(8)},
{F: simple.Node(4), T: simple.Node(9)},
{F: simple.Node(4), T: simple.Node(10)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return orderedGraph{g}
}(),
param: EadesR2{Repulsion: 1, Rate: 0.1, Updates: 100, Theta: 0.1, Src: rand.NewSource(1)},
wantIters: 100,
},
}
for _, test := range eadesR2Tests {
eades := test.param
o := NewOptimizerR2(test.g, eades.Update)
var n int
for o.Update() {
n++
}
if n != test.wantIters {
t.Errorf("unexpected number of iterations for %q: got:%d want:%d", test.name, n, test.wantIters)
}
p := plot.New()
p.Add(render{o})
p.HideAxes()
path := filepath.Join("testdata", test.name+".png")
err := p.Save(10*vg.Centimeter, 10*vg.Centimeter, path)
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
ok := checkRenderedLayout(t, path)
if !ok {
got := make(map[int64]r2.Vec)
nodes := test.g.Nodes()
for nodes.Next() {
id := nodes.Node().ID()
got[id] = o.Coord2(id)
}
t.Logf("got node positions: %#v", got)
}
}
}