mirror of
				https://github.com/gonum/gonum.git
				synced 2025-10-31 10:36:30 +08:00 
			
		
		
		
	Merge pull request #1024 from gonum/graph/layout-isomap
graph/layout: add Isomap layout
This commit is contained in:
		
							
								
								
									
										63
									
								
								graph/layout/isomap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								graph/layout/isomap.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"gonum.org/v1/gonum/graph" | ||||
| 	"gonum.org/v1/gonum/graph/path" | ||||
| 	"gonum.org/v1/gonum/mat" | ||||
| 	"gonum.org/v1/gonum/spatial/r2" | ||||
| 	"gonum.org/v1/gonum/stat/mds" | ||||
| ) | ||||
|  | ||||
| // IsomapR2 implements a graph layout algorithm based on the Isomap | ||||
| // non-linear dimensionality reduction method. Coordinates of nodes | ||||
| // are computed by finding a Torgerson multidimensional scaling of | ||||
| // the shortest path distances between all pairs of node in the graph. | ||||
| // The all pair shortest path distances are calculated using the | ||||
| // Floyd-Warshall algorithm and so IsomapR2 will not scale to large | ||||
| // graphs. Graphs with more than one connected component cannot be | ||||
| // laid out by IsomapR2. | ||||
| type IsomapR2 struct{} | ||||
|  | ||||
| // Update is the IsomapR2 spatial graph update function. | ||||
| func (IsomapR2) Update(g graph.Graph, layout LayoutR2) bool { | ||||
| 	nodes := graph.NodesOf(g.Nodes()) | ||||
| 	v := isomap(g, nodes, 2) | ||||
| 	if v == nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// FIXME(kortschak): The Layout types do not have the capacity to | ||||
| 	// be cleared in the current API. Is this a problem? I don't know | ||||
| 	// at this stage. It might be if the layout is reused between graphs. | ||||
| 	// Someone may do this. | ||||
|  | ||||
| 	for i, n := range nodes { | ||||
| 		layout.SetCoord2(n.ID(), r2.Vec{X: v.At(i, 0), Y: v.At(i, 1)}) | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func isomap(g graph.Graph, nodes []graph.Node, dims int) *mat.Dense { | ||||
| 	p, ok := path.FloydWarshall(g) | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	dist := mat.NewSymDense(len(nodes), nil) | ||||
| 	for i, u := range nodes { | ||||
| 		for j, v := range nodes { | ||||
| 			dist.SetSym(i, j, p.Weight(u.ID(), v.ID())) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	k, v, _ := mds.TorgersonScaling(nil, nil, dist) | ||||
| 	if k < dims { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return v | ||||
| } | ||||
							
								
								
									
										180
									
								
								graph/layout/isomap_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								graph/layout/isomap_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
|  | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| var isomapR2Tests = []struct { | ||||
| 	name string | ||||
| 	g    graph.Graph | ||||
| }{ | ||||
| 	{ | ||||
| 		name: "line_isomap", | ||||
| 		g: func() graph.Graph { | ||||
| 			edges := []simple.Edge{ | ||||
| 				{simple.Node(0), simple.Node(1)}, | ||||
| 			} | ||||
| 			g := simple.NewUndirectedGraph() | ||||
| 			for _, e := range edges { | ||||
| 				g.SetEdge(e) | ||||
| 			} | ||||
| 			return orderedGraph{g} | ||||
| 		}(), | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: "square_isomap", | ||||
| 		g: func() graph.Graph { | ||||
| 			edges := []simple.Edge{ | ||||
| 				{simple.Node(0), simple.Node(1)}, | ||||
| 				{simple.Node(0), simple.Node(2)}, | ||||
| 				{simple.Node(1), simple.Node(3)}, | ||||
| 				{simple.Node(2), simple.Node(3)}, | ||||
| 			} | ||||
| 			g := simple.NewUndirectedGraph() | ||||
| 			for _, e := range edges { | ||||
| 				g.SetEdge(e) | ||||
| 			} | ||||
| 			return orderedGraph{g} | ||||
| 		}(), | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: "tetrahedron_isomap", | ||||
| 		g: func() graph.Graph { | ||||
| 			edges := []simple.Edge{ | ||||
| 				{simple.Node(0), simple.Node(1)}, | ||||
| 				{simple.Node(0), simple.Node(2)}, | ||||
| 				{simple.Node(0), simple.Node(3)}, | ||||
| 				{simple.Node(1), simple.Node(2)}, | ||||
| 				{simple.Node(1), simple.Node(3)}, | ||||
| 				{simple.Node(2), simple.Node(3)}, | ||||
| 			} | ||||
| 			g := simple.NewUndirectedGraph() | ||||
| 			for _, e := range edges { | ||||
| 				g.SetEdge(e) | ||||
| 			} | ||||
| 			return orderedGraph{g} | ||||
| 		}(), | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: "sheet_isomap", | ||||
| 		g: func() graph.Graph { | ||||
| 			edges := []simple.Edge{ | ||||
| 				{simple.Node(0), simple.Node(1)}, | ||||
| 				{simple.Node(0), simple.Node(3)}, | ||||
| 				{simple.Node(1), simple.Node(2)}, | ||||
| 				{simple.Node(1), simple.Node(4)}, | ||||
| 				{simple.Node(2), simple.Node(5)}, | ||||
| 				{simple.Node(3), simple.Node(4)}, | ||||
| 				{simple.Node(3), simple.Node(6)}, | ||||
| 				{simple.Node(4), simple.Node(5)}, | ||||
| 				{simple.Node(4), simple.Node(7)}, | ||||
| 				{simple.Node(5), simple.Node(8)}, | ||||
| 				{simple.Node(6), simple.Node(7)}, | ||||
| 				{simple.Node(7), simple.Node(8)}, | ||||
| 			} | ||||
| 			g := simple.NewUndirectedGraph() | ||||
| 			for _, e := range edges { | ||||
| 				g.SetEdge(e) | ||||
| 			} | ||||
| 			return orderedGraph{g} | ||||
| 		}(), | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: "tube_isomap", | ||||
| 		g: func() graph.Graph { | ||||
| 			edges := []simple.Edge{ | ||||
| 				{simple.Node(0), simple.Node(1)}, | ||||
| 				{simple.Node(0), simple.Node(2)}, | ||||
| 				{simple.Node(0), simple.Node(3)}, | ||||
| 				{simple.Node(1), simple.Node(2)}, | ||||
| 				{simple.Node(1), simple.Node(4)}, | ||||
| 				{simple.Node(2), simple.Node(5)}, | ||||
| 				{simple.Node(3), simple.Node(4)}, | ||||
| 				{simple.Node(3), simple.Node(5)}, | ||||
| 				{simple.Node(3), simple.Node(6)}, | ||||
| 				{simple.Node(4), simple.Node(5)}, | ||||
| 				{simple.Node(4), simple.Node(7)}, | ||||
| 				{simple.Node(5), simple.Node(8)}, | ||||
| 				{simple.Node(6), simple.Node(7)}, | ||||
| 				{simple.Node(6), simple.Node(8)}, | ||||
| 				{simple.Node(7), simple.Node(8)}, | ||||
| 			} | ||||
| 			g := simple.NewUndirectedGraph() | ||||
| 			for _, e := range edges { | ||||
| 				g.SetEdge(e) | ||||
| 			} | ||||
| 			return orderedGraph{g} | ||||
| 		}(), | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: "wp_page_isomap", // https://en.wikipedia.org/wiki/PageRank#/media/File:PageRanks-Example.jpg | ||||
| 		g: func() graph.Graph { | ||||
| 			edges := []simple.Edge{ | ||||
| 				{simple.Node(0), simple.Node(3)}, | ||||
| 				{simple.Node(1), simple.Node(2)}, | ||||
| 				{simple.Node(1), simple.Node(3)}, | ||||
| 				{simple.Node(1), simple.Node(4)}, | ||||
| 				{simple.Node(1), simple.Node(5)}, | ||||
| 				{simple.Node(1), simple.Node(6)}, | ||||
| 				{simple.Node(1), simple.Node(7)}, | ||||
| 				{simple.Node(1), simple.Node(8)}, | ||||
| 				{simple.Node(3), simple.Node(4)}, | ||||
| 				{simple.Node(4), simple.Node(5)}, | ||||
| 				{simple.Node(4), simple.Node(6)}, | ||||
| 				{simple.Node(4), simple.Node(7)}, | ||||
| 				{simple.Node(4), simple.Node(8)}, | ||||
| 				{simple.Node(4), simple.Node(9)}, | ||||
| 				{simple.Node(4), simple.Node(10)}, | ||||
| 			} | ||||
| 			g := simple.NewUndirectedGraph() | ||||
| 			for _, e := range edges { | ||||
| 				g.SetEdge(e) | ||||
| 			} | ||||
| 			return orderedGraph{g} | ||||
| 		}(), | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestIsomapR2(t *testing.T) { | ||||
| 	for _, test := range isomapR2Tests { | ||||
| 		o := NewOptimizerR2(test.g, IsomapR2{}.Update) | ||||
| 		var n int | ||||
| 		for o.Update() { | ||||
| 			n++ | ||||
| 		} | ||||
| 		p, err := plot.New() | ||||
| 		if err != nil { | ||||
| 			t.Errorf("unexpected error: %v", err) | ||||
| 			continue | ||||
| 		} | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								graph/layout/testdata/line_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								graph/layout/testdata/line_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								graph/layout/testdata/sheet_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								graph/layout/testdata/sheet_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 22 KiB | 
							
								
								
									
										
											BIN
										
									
								
								graph/layout/testdata/square_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								graph/layout/testdata/square_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								graph/layout/testdata/tetrahedron_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								graph/layout/testdata/tetrahedron_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										
											BIN
										
									
								
								graph/layout/testdata/tube_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								graph/layout/testdata/tube_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								graph/layout/testdata/wp_page_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								graph/layout/testdata/wp_page_isomap_golden.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 17 KiB | 
		Reference in New Issue
	
	Block a user
	 Dan Kortschak
					Dan Kortschak