├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── .travis └── test-coverage.sh ├── README.md ├── community ├── bisect.go ├── bisect_test.go ├── louvain.tex ├── louvain_common.go ├── louvain_directed.go ├── louvain_directed_multiplex.go ├── louvain_directed_multiplex_test.go ├── louvain_directed_test.go ├── louvain_test.go ├── louvain_undirected.go ├── louvain_undirected_multiplex.go ├── louvain_undirected_multiplex_test.go ├── louvain_undirected_test.go └── printgraphs.go ├── doc.go ├── encoding └── dot │ ├── decode.go │ ├── decode_test.go │ ├── dot.go │ └── dot_test.go ├── ex └── fdpclust │ ├── gn.go │ └── main.go ├── formats └── dot │ ├── README.md │ ├── ast │ ├── ast.go │ └── ast_test.go │ ├── dot.go │ ├── internal │ ├── Makefile │ ├── astx │ │ ├── astx.go │ │ └── astx_test.go │ ├── dot.bnf │ ├── errors │ │ └── errors.go │ ├── lexer │ │ ├── acttab.go │ │ ├── lexer.go │ │ ├── lexer_test.go │ │ ├── testdata │ │ │ ├── tokens.dot │ │ │ └── tokens.golden │ │ └── transitiontable.go │ ├── parser │ │ ├── action.go │ │ ├── actiontable.go │ │ ├── gototable.go │ │ ├── parser.go │ │ ├── parser_test.go │ │ └── productionstable.go │ ├── paste_copyright.bash │ ├── testdata │ │ ├── attr.dot │ │ ├── attr_lists.dot │ │ ├── attr_lists.golden │ │ ├── attr_sep.dot │ │ ├── attr_sep.golden │ │ ├── attr_stmt.dot │ │ ├── backslash_newline_id.dot │ │ ├── backslash_newline_id.golden │ │ ├── digraph.dot │ │ ├── edge_stmt.dot │ │ ├── empty.dot │ │ ├── empty_attr.dot │ │ ├── empty_attr.golden │ │ ├── error.dot │ │ ├── graph.dot │ │ ├── multi.dot │ │ ├── named_graph.dot │ │ ├── node_stmt.dot │ │ ├── port.dot │ │ ├── port.golden │ │ ├── quoted_id.dot │ │ ├── semi.dot │ │ ├── semi.golden │ │ ├── strict.dot │ │ ├── subgraph.dot │ │ ├── subgraph.golden │ │ └── subgraph_vertex.dot │ ├── token │ │ └── token.go │ └── util │ │ ├── litconv.go │ │ └── rune.go │ ├── sem.go │ └── testdata │ ├── .gitignore │ └── Makefile ├── graph.go ├── graphs └── gen │ ├── batagelj_brandes.go │ ├── batagelj_brandes_test.go │ ├── duplication.go │ ├── duplication_test.go │ ├── gen.go │ ├── holme_kim.go │ ├── holme_kim_test.go │ ├── small_world.go │ └── small_world_test.go ├── internal ├── linear │ └── linear.go ├── ordered │ └── sort.go └── set │ ├── same.go │ ├── same_appengine.go │ ├── set.go │ └── set_test.go ├── network ├── betweenness.go ├── betweenness_test.go ├── distance.go ├── distance_test.go ├── hits.go ├── hits_test.go ├── network.go ├── network_test.go ├── page.go └── page_test.go ├── path ├── a_star.go ├── a_star_test.go ├── bellman_ford_moore.go ├── bellman_ford_moore_test.go ├── bench_test.go ├── control_flow.go ├── dijkstra.go ├── dijkstra_test.go ├── disjoint.go ├── disjoint_test.go ├── doc.go ├── dynamic │ ├── doc.go │ ├── dstarlite.go │ ├── dstarlite_test.go │ └── dumper_test.go ├── floydwarshall.go ├── floydwarshall_test.go ├── internal │ ├── grid.go │ ├── grid_test.go │ ├── limited.go │ ├── limited_test.go │ └── testgraphs │ │ └── shortest.go ├── johnson_apsp.go ├── johnson_apsp_test.go ├── shortest.go ├── spanning_tree.go ├── spanning_tree_test.go └── weight.go ├── simple ├── dense_directed_matrix.go ├── dense_undirected_matrix.go ├── densegraph_test.go ├── directed.go ├── directed_test.go ├── simple.go ├── undirected.go └── undirected_test.go ├── topo ├── bench_test.go ├── bron_kerbosch.go ├── bron_kerbosch_test.go ├── common_test.go ├── johnson_cycles.go ├── johnson_cycles_test.go ├── non_tomita_choice.go ├── tarjan.go ├── tarjan_test.go ├── tomita_choice.go ├── topo.go └── topo_test.go ├── traverse ├── traverse.go └── traverse_test.go ├── undirect.go └── undirect_test.go /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### This repository is no longer actively maintained. 2 | 3 | Development of the packages in this repository has moved to https://github.com/gonum/gonum. 4 | Please file issues [there](https://github.com/gonum/gonum/issues) after having checked that your issue has not been fixed. 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### This repository is no longer actively maintained. 2 | 3 | Development of the packages in this repository has moved to https://github.com/gonum/gonum. 4 | Please send pull requests [there](https://github.com/gonum/gonum/pulls) after having checked that your addition has not already been made. 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test.out -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: go 4 | 5 | # Versions of go that are explicitly supported by gonum. 6 | go: 7 | - 1.5.4 8 | - 1.6.3 9 | - 1.7.3 10 | 11 | # Required for coverage. 12 | before_install: 13 | - go get golang.org/x/tools/cmd/cover 14 | - go get github.com/mattn/goveralls 15 | 16 | # Get deps, build, test, and ensure the code is gofmt'ed. 17 | # If we are building as gonum, then we have access to the coveralls api key, so we can run coverage as well. 18 | script: 19 | - go get -d -t -v ./... 20 | - go build -v ./... 21 | - go test -v -a ./... 22 | - go test -v -a -tags appengine ./... 23 | - test -z "$(gofmt -d .)" 24 | - if [[ $TRAVIS_SECURE_ENV_VARS = "true" ]]; then bash ./.travis/test-coverage.sh; fi 25 | -------------------------------------------------------------------------------- /.travis/test-coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROFILE_OUT=$PWD/profile.out 4 | ACC_OUT=$PWD/acc.out 5 | 6 | testCover() { 7 | # set the return value to 0 (succesful) 8 | retval=0 9 | # get the directory to check from the parameter. Default to '.' 10 | d=${1:-.} 11 | # skip if there are no Go files here 12 | ls $d/*.go &> /dev/null || return $retval 13 | # switch to the directory to check 14 | pushd $d > /dev/null 15 | # create the coverage profile 16 | coverageresult=`go test -v -coverprofile=$PROFILE_OUT` 17 | # output the result so we can check the shell output 18 | echo ${coverageresult} 19 | # append the results to acc.out if coverage didn't fail, else set the retval to 1 (failed) 20 | ( [[ ${coverageresult} == *FAIL* ]] && retval=1 ) || ( [ -f $PROFILE_OUT ] && grep -v "mode: set" $PROFILE_OUT >> $ACC_OUT ) 21 | # return to our working dir 22 | popd > /dev/null 23 | # return our return value 24 | return $retval 25 | } 26 | 27 | # Init acc.out 28 | echo "mode: set" > $ACC_OUT 29 | 30 | # Run test coverage on all directories containing go files 31 | find . -maxdepth 10 -type d | while read d; do testCover $d || exit; done 32 | 33 | # Upload the coverage profile to coveralls.io 34 | [ -n "$COVERALLS_TOKEN" ] && goveralls -coverprofile=$ACC_OUT -service=travis-ci -repotoken $COVERALLS_TOKEN 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gonum Graph [![Build Status](https://travis-ci.org/gonum/graph.svg?branch=master)](https://travis-ci.org/gonum/graph) [![Coverage Status](https://coveralls.io/repos/gonum/graph/badge.svg?branch=master&service=github)](https://coveralls.io/github/gonum/graph?branch=master) [![GoDoc](https://godoc.org/github.com/gonum/graph?status.svg)](https://godoc.org/github.com/gonum/graph) 2 | 3 | # This repository is no longer maintained. Development has moved to https://github.com/gonum/gonum. 4 | 5 | This is a generalized graph package for the Go language. It aims to provide a clean, transparent API for common algorithms on arbitrary graphs such as finding the graph's strongly connected components, dominators, or searces. 6 | 7 | The package is currently in testing, and the API is "semi-stable". The signatures of any functions like AStar are unlikely to change much, but the Graph, Node, and Edge interfaces may change a bit. 8 | 9 | ## Issues 10 | 11 | If you find any bugs, feel free to file an issue on the github [issue tracker for gonum/gonum](https://github.com/gonum/gonum/issues) if the bug exists in that reposity; no code changes will be made to this repository. Other dicussions should be taken to the gonum-dev Google Group. 12 | 13 | https://groups.google.com/forum/#!forum/gonum-dev 14 | 15 | ## License 16 | 17 | Please see github.com/gonum/license for general license information, contributors, authors, etc on the Gonum suite of packages. 18 | -------------------------------------------------------------------------------- /community/printgraphs.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | // printgraphs allows us to generate a consistent directed view of 8 | // a set of edges that follows a reasonably real-world-meaningful 9 | // graph. The interpretation of the links in the resulting directed 10 | // graphs are either "suggests" in the context of a Page Ranking or 11 | // possibly "looks up to" in the Zachary graph. 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "sort" 17 | 18 | "github.com/gonum/graph" 19 | "github.com/gonum/graph/internal/ordered" 20 | "github.com/gonum/graph/network" 21 | "github.com/gonum/graph/simple" 22 | ) 23 | 24 | // set is an integer set. 25 | type set map[int]struct{} 26 | 27 | func linksTo(i ...int) set { 28 | if len(i) == 0 { 29 | return nil 30 | } 31 | s := make(set) 32 | for _, v := range i { 33 | s[v] = struct{}{} 34 | } 35 | return s 36 | } 37 | 38 | var ( 39 | zachary = []set{ 40 | 0: linksTo(1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 17, 19, 21, 31), 41 | 1: linksTo(2, 3, 7, 13, 17, 19, 21, 30), 42 | 2: linksTo(3, 7, 8, 9, 13, 27, 28, 32), 43 | 3: linksTo(7, 12, 13), 44 | 4: linksTo(6, 10), 45 | 5: linksTo(6, 10, 16), 46 | 6: linksTo(16), 47 | 8: linksTo(30, 32, 33), 48 | 9: linksTo(33), 49 | 13: linksTo(33), 50 | 14: linksTo(32, 33), 51 | 15: linksTo(32, 33), 52 | 18: linksTo(32, 33), 53 | 19: linksTo(33), 54 | 20: linksTo(32, 33), 55 | 22: linksTo(32, 33), 56 | 23: linksTo(25, 27, 29, 32, 33), 57 | 24: linksTo(25, 27, 31), 58 | 25: linksTo(31), 59 | 26: linksTo(29, 33), 60 | 27: linksTo(33), 61 | 28: linksTo(31, 33), 62 | 29: linksTo(32, 33), 63 | 30: linksTo(32, 33), 64 | 31: linksTo(32, 33), 65 | 32: linksTo(33), 66 | 33: nil, 67 | } 68 | 69 | blondel = []set{ 70 | 0: linksTo(2, 3, 4, 5), 71 | 1: linksTo(2, 4, 7), 72 | 2: linksTo(4, 5, 6), 73 | 3: linksTo(7), 74 | 4: linksTo(10), 75 | 5: linksTo(7, 11), 76 | 6: linksTo(7, 11), 77 | 8: linksTo(9, 10, 11, 14, 15), 78 | 9: linksTo(12, 14), 79 | 10: linksTo(11, 12, 13, 14), 80 | 11: linksTo(13), 81 | 15: nil, 82 | } 83 | ) 84 | 85 | func main() { 86 | for _, raw := range []struct { 87 | name string 88 | set []set 89 | }{ 90 | {"zachary", zachary}, 91 | {"blondel", blondel}, 92 | } { 93 | g := simple.NewUndirectedGraph(0, 0) 94 | for u, e := range raw.set { 95 | // Add nodes that are not defined by an edge. 96 | if !g.Has(simple.Node(u)) { 97 | g.AddNode(simple.Node(u)) 98 | } 99 | for v := range e { 100 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) 101 | } 102 | } 103 | 104 | nodes := g.Nodes() 105 | sort.Sort(ordered.ByID(nodes)) 106 | 107 | fmt.Printf("%s = []set{\n", raw.name) 108 | rank := network.PageRank(asDirected{g}, 0.85, 1e-8) 109 | for _, u := range nodes { 110 | to := g.From(nodes[u.ID()]) 111 | sort.Sort(ordered.ByID(to)) 112 | var links []int 113 | for _, v := range to { 114 | if rank[u.ID()] <= rank[v.ID()] { 115 | links = append(links, v.ID()) 116 | } 117 | } 118 | 119 | if links == nil { 120 | fmt.Printf("\t%d: nil, // rank=%.4v\n", u.ID(), rank[u.ID()]) 121 | continue 122 | } 123 | 124 | fmt.Printf("\t%d: linksTo(", u.ID()) 125 | for i, v := range links { 126 | if i != 0 { 127 | fmt.Print(", ") 128 | } 129 | fmt.Print(v) 130 | } 131 | fmt.Printf("), // rank=%.4v\n", rank[u.ID()]) 132 | } 133 | fmt.Println("}") 134 | } 135 | } 136 | 137 | type asDirected struct{ *simple.UndirectedGraph } 138 | 139 | func (g asDirected) HasEdgeFromTo(u, v graph.Node) bool { 140 | return g.UndirectedGraph.HasEdgeBetween(u, v) 141 | } 142 | func (g asDirected) To(v graph.Node) []graph.Node { return g.From(v) } 143 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | This repository is no longer maintained. 7 | Development has moved to https://github.com/gonum/gonum. 8 | 9 | Package graph implements functions and interfaces to deal with formal discrete graphs. It aims to 10 | be first and foremost flexible, with speed as a strong second priority. 11 | 12 | In this package, graphs are taken to be directed, and undirected graphs are considered to be a 13 | special case of directed graphs that happen to have reciprocal edges. Graphs are, by default, 14 | unweighted, but functions that require weighted edges have several methods of dealing with this. 15 | In order of precedence: 16 | 17 | 1. These functions have an argument called Cost (and in some cases, HeuristicCost). If this is 18 | present, it will always be used to determine the cost between two nodes. 19 | 20 | 2. These functions will check if your graph implements the Coster (and/or HeuristicCoster) 21 | interface. If this is present, and the Cost (or HeuristicCost) argument is nil, these functions 22 | will be used. 23 | 24 | 3. Finally, if no user data is supplied, it will use the functions UniformCost (always returns 1) 25 | and/or NulLHeuristic (always returns 0). 26 | 27 | For information on the specification for Cost functions, please see the Coster interface. 28 | 29 | Finally, although the functions take in a Graph -- they will always use the correct behavior. 30 | If your graph implements DirectedGraph, it will use Successors and To where applicable, 31 | if undirected, it will use From instead. If it implements neither, it will scan the edge list 32 | for successors and predecessors where applicable. (This is slow, you should always implement either 33 | Directed or Undirected) 34 | 35 | This package will never modify a graph that is not Mutable (and the interface does not allow it to 36 | do so). However, return values are free to be modified, so never pass a reference to your own edge 37 | list or node list. It also guarantees that any nodes passed back to the user will be the same 38 | nodes returned to it -- that is, it will never take a Node's ID and then wrap the ID in a new 39 | struct and return that. You'll always get back your original data. 40 | */ 41 | package graph 42 | -------------------------------------------------------------------------------- /encoding/dot/decode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2017 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dot 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | "github.com/gonum/graph/simple" 13 | ) 14 | 15 | func TestRoundTrip(t *testing.T) { 16 | golden := []struct { 17 | want string 18 | directed bool 19 | }{ 20 | { 21 | want: directed, 22 | directed: true, 23 | }, 24 | { 25 | want: undirected, 26 | directed: false, 27 | }, 28 | } 29 | for i, g := range golden { 30 | var dst Builder 31 | if g.directed { 32 | dst = newDotDirectedGraph() 33 | } else { 34 | dst = newDotUndirectedGraph() 35 | } 36 | data := []byte(g.want) 37 | if err := Unmarshal(data, dst); err != nil { 38 | t.Errorf("i=%d: unable to unmarshal DOT graph; %v", i, err) 39 | continue 40 | } 41 | buf, err := Marshal(dst, "", "", "\t", false) 42 | if err != nil { 43 | t.Errorf("i=%d: unable to marshal graph; %v", i, dst) 44 | continue 45 | } 46 | got := string(buf) 47 | if got != g.want { 48 | t.Errorf("i=%d: graph content mismatch; expected `%s`, got `%s`", i, g.want, got) 49 | continue 50 | } 51 | } 52 | } 53 | 54 | const directed = `digraph { 55 | // Node definitions. 56 | 0 [label="foo 2"]; 57 | 1 [label="bar 2"]; 58 | 59 | // Edge definitions. 60 | 0 -> 1 [label="baz 2"]; 61 | }` 62 | 63 | const undirected = `graph { 64 | // Node definitions. 65 | 0 [label="foo 2"]; 66 | 1 [label="bar 2"]; 67 | 68 | // Edge definitions. 69 | 0 -- 1 [label="baz 2"]; 70 | }` 71 | 72 | // Below follows a minimal implementation of a graph capable of validating the 73 | // round-trip encoding and decoding of DOT graphs with nodes and edges 74 | // containing DOT attributes. 75 | 76 | // dotDirectedGraph extends simple.DirectedGraph to add NewNode and NewEdge 77 | // methods for creating user-defined nodes and edges. 78 | // 79 | // dotDirectedGraph implements the dot.Builder interface. 80 | type dotDirectedGraph struct { 81 | *simple.DirectedGraph 82 | } 83 | 84 | // newDotDirectedGraph returns a new directed capable of creating user-defined 85 | // nodes and edges. 86 | func newDotDirectedGraph() *dotDirectedGraph { 87 | return &dotDirectedGraph{DirectedGraph: simple.NewDirectedGraph(0, 0)} 88 | } 89 | 90 | // NewNode adds a new node with a unique node ID to the graph. 91 | func (g *dotDirectedGraph) NewNode() graph.Node { 92 | n := &dotNode{Node: simple.Node(g.NewNodeID())} 93 | g.AddNode(n) 94 | return n 95 | } 96 | 97 | // NewEdge adds a new edge from the source to the destination node to the graph, 98 | // or returns the existing edge if already present. 99 | func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge { 100 | if e := g.Edge(from, to); e != nil { 101 | return e 102 | } 103 | e := &dotEdge{Edge: simple.Edge{F: from, T: to}} 104 | g.SetEdge(e) 105 | return e 106 | } 107 | 108 | // dotUndirectedGraph extends simple.UndirectedGraph to add NewNode and NewEdge 109 | // methods for creating user-defined nodes and edges. 110 | // 111 | // dotUndirectedGraph implements the dot.Builder interface. 112 | type dotUndirectedGraph struct { 113 | *simple.UndirectedGraph 114 | } 115 | 116 | // newDotUndirectedGraph returns a new undirected capable of creating user- 117 | // defined nodes and edges. 118 | func newDotUndirectedGraph() *dotUndirectedGraph { 119 | return &dotUndirectedGraph{UndirectedGraph: simple.NewUndirectedGraph(0, 0)} 120 | } 121 | 122 | // NewNode adds a new node with a unique node ID to the graph. 123 | func (g *dotUndirectedGraph) NewNode() graph.Node { 124 | n := &dotNode{Node: simple.Node(g.NewNodeID())} 125 | g.AddNode(n) 126 | return n 127 | } 128 | 129 | // NewEdge adds a new edge from the source to the destination node to the graph, 130 | // or returns the existing edge if already present. 131 | func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge { 132 | if e := g.Edge(from, to); e != nil { 133 | return e 134 | } 135 | e := &dotEdge{Edge: simple.Edge{F: from, T: to}} 136 | g.SetEdge(e) 137 | return e 138 | } 139 | 140 | // dotNode extends simple.Node with a label field to test round-trip encoding 141 | // and decoding of node DOT label attributes. 142 | type dotNode struct { 143 | simple.Node 144 | // Node label. 145 | Label string 146 | } 147 | 148 | // UnmarshalDOTAttr decodes a single DOT attribute. 149 | func (n *dotNode) UnmarshalDOTAttr(attr Attribute) error { 150 | if attr.Key != "label" { 151 | return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) 152 | } 153 | n.Label = attr.Value 154 | return nil 155 | } 156 | 157 | // DOTAttributes returns the DOT attributes of the node. 158 | func (n *dotNode) DOTAttributes() []Attribute { 159 | if len(n.Label) == 0 { 160 | return nil 161 | } 162 | attr := Attribute{ 163 | Key: "label", 164 | Value: n.Label, 165 | } 166 | return []Attribute{attr} 167 | } 168 | 169 | // dotEdge extends simple.Edge with a label field to test round-trip encoding and 170 | // decoding of edge DOT label attributes. 171 | type dotEdge struct { 172 | simple.Edge 173 | // Edge label. 174 | Label string 175 | } 176 | 177 | // UnmarshalDOTAttr decodes a single DOT attribute. 178 | func (e *dotEdge) UnmarshalDOTAttr(attr Attribute) error { 179 | if attr.Key != "label" { 180 | return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) 181 | } 182 | e.Label = attr.Value 183 | return nil 184 | } 185 | 186 | // DOTAttributes returns the DOT attributes of the edge. 187 | func (e *dotEdge) DOTAttributes() []Attribute { 188 | if len(e.Label) == 0 { 189 | return nil 190 | } 191 | attr := Attribute{ 192 | Key: "label", 193 | Value: e.Label, 194 | } 195 | return []Attribute{attr} 196 | } 197 | -------------------------------------------------------------------------------- /ex/fdpclust/main.go: -------------------------------------------------------------------------------- 1 | // This repository is no longer maintained. 2 | // Development has moved to https://github.com/gonum/gonum. 3 | // 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/gonum/graph" 10 | "github.com/gonum/graph/topo" 11 | ) 12 | 13 | func main() { 14 | // graph G { 15 | G := NewGraphNode(0) 16 | // e 17 | e := NewGraphNode(1) 18 | 19 | // subgraph clusterA { 20 | clusterA := NewGraphNode(2) 21 | 22 | // a -- b 23 | a := NewGraphNode(3) 24 | b := NewGraphNode(4) 25 | a.AddNeighbor(b) 26 | b.AddNeighbor(a) 27 | clusterA.AddRoot(a) 28 | clusterA.AddRoot(b) 29 | 30 | // subgraph clusterC { 31 | clusterC := NewGraphNode(5) 32 | // C -- D 33 | C := NewGraphNode(6) 34 | D := NewGraphNode(7) 35 | C.AddNeighbor(D) 36 | D.AddNeighbor(C) 37 | 38 | clusterC.AddRoot(C) 39 | clusterC.AddRoot(D) 40 | // } 41 | clusterA.AddRoot(clusterC) 42 | // } 43 | 44 | // subgraph clusterB { 45 | clusterB := NewGraphNode(8) 46 | 47 | // d -- f 48 | d := NewGraphNode(9) 49 | f := NewGraphNode(10) 50 | d.AddNeighbor(f) 51 | f.AddNeighbor(d) 52 | clusterB.AddRoot(d) 53 | clusterB.AddRoot(f) 54 | // } 55 | 56 | // d -- D 57 | d.AddNeighbor(D) 58 | D.AddNeighbor(d) 59 | 60 | // e -- clusterB 61 | e.AddNeighbor(clusterB) 62 | clusterB.AddNeighbor(e) 63 | 64 | // clusterC -- clusterB 65 | clusterC.AddNeighbor(clusterB) 66 | clusterB.AddNeighbor(clusterC) 67 | 68 | G.AddRoot(e) 69 | G.AddRoot(clusterA) 70 | G.AddRoot(clusterB) 71 | // } 72 | 73 | if !topo.IsPathIn(G, []graph.Node{C, D, d, f}) { 74 | fmt.Println("Not working!") 75 | } else { 76 | fmt.Println("Working!") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /formats/dot/README.md: -------------------------------------------------------------------------------- 1 | # formats/dot 2 | 3 | ## License 4 | 5 | The source code and any original content of the formats/dot directory is released under [Public Domain Dedication](https://creativecommons.org/publicdomain/zero/1.0/). 6 | 7 | The source code is also licensed under the gonum license, and users are free to choice the license which suits their needs. 8 | 9 | Please see github.com/gonum/license for general license information, contributors, authors, etc on the Gonum suite of packages. 10 | -------------------------------------------------------------------------------- /formats/dot/ast/ast_test.go: -------------------------------------------------------------------------------- 1 | // This file is dual licensed under CC0 and The gonum license. 2 | // 3 | // Copyright ©2017 The gonum Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | // Copyright ©2017 Robin Eklind. 8 | // This file is made available under a Creative Commons CC0 1.0 9 | // Universal Public Domain Dedication. 10 | 11 | package ast_test 12 | 13 | import ( 14 | "bytes" 15 | "io/ioutil" 16 | "testing" 17 | 18 | "github.com/gonum/graph/formats/dot" 19 | "github.com/gonum/graph/formats/dot/ast" 20 | ) 21 | 22 | func TestParseFile(t *testing.T) { 23 | golden := []struct { 24 | in string 25 | out string 26 | }{ 27 | {in: "../internal/testdata/empty.dot"}, 28 | {in: "../internal/testdata/graph.dot"}, 29 | {in: "../internal/testdata/digraph.dot"}, 30 | {in: "../internal/testdata/strict.dot"}, 31 | {in: "../internal/testdata/multi.dot"}, 32 | {in: "../internal/testdata/named_graph.dot"}, 33 | {in: "../internal/testdata/node_stmt.dot"}, 34 | {in: "../internal/testdata/edge_stmt.dot"}, 35 | {in: "../internal/testdata/attr_stmt.dot"}, 36 | {in: "../internal/testdata/attr.dot"}, 37 | { 38 | in: "../internal/testdata/subgraph.dot", 39 | out: "../internal/testdata/subgraph.golden", 40 | }, 41 | { 42 | in: "../internal/testdata/semi.dot", 43 | out: "../internal/testdata/semi.golden", 44 | }, 45 | { 46 | in: "../internal/testdata/empty_attr.dot", 47 | out: "../internal/testdata/empty_attr.golden", 48 | }, 49 | { 50 | in: "../internal/testdata/attr_lists.dot", 51 | out: "../internal/testdata/attr_lists.golden", 52 | }, 53 | { 54 | in: "../internal/testdata/attr_sep.dot", 55 | out: "../internal/testdata/attr_sep.golden", 56 | }, 57 | {in: "../internal/testdata/subgraph_vertex.dot"}, 58 | { 59 | in: "../internal/testdata/port.dot", 60 | out: "../internal/testdata/port.golden", 61 | }, 62 | } 63 | for _, g := range golden { 64 | file, err := dot.ParseFile(g.in) 65 | if err != nil { 66 | t.Errorf("%q: unable to parse file; %v", g.in, err) 67 | continue 68 | } 69 | // If no output path is specified, the input is already golden. 70 | out := g.in 71 | if len(g.out) > 0 { 72 | out = g.out 73 | } 74 | buf, err := ioutil.ReadFile(out) 75 | if err != nil { 76 | t.Errorf("%q: unable to read file; %v", g.in, err) 77 | continue 78 | } 79 | got := file.String() 80 | // Remove trailing newline. 81 | want := string(bytes.TrimSpace(buf)) 82 | if got != want { 83 | t.Errorf("%q: graph mismatch; expected %q, got %q", g.in, want, got) 84 | } 85 | } 86 | } 87 | 88 | // Verify that all statements implement the Stmt interface. 89 | var ( 90 | _ ast.Stmt = (*ast.NodeStmt)(nil) 91 | _ ast.Stmt = (*ast.EdgeStmt)(nil) 92 | _ ast.Stmt = (*ast.AttrStmt)(nil) 93 | _ ast.Stmt = (*ast.Attr)(nil) 94 | _ ast.Stmt = (*ast.Subgraph)(nil) 95 | ) 96 | 97 | // Verify that all vertices implement the Vertex interface. 98 | var ( 99 | _ ast.Vertex = (*ast.Node)(nil) 100 | _ ast.Vertex = (*ast.Subgraph)(nil) 101 | ) 102 | -------------------------------------------------------------------------------- /formats/dot/dot.go: -------------------------------------------------------------------------------- 1 | // This file is dual licensed under CC0 and The gonum license. 2 | // 3 | // Copyright ©2017 The gonum Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | // Copyright ©2017 Robin Eklind. 8 | // This file is made available under a Creative Commons CC0 1.0 9 | // Universal Public Domain Dedication. 10 | 11 | // This repository is no longer maintained. 12 | // Development has moved to https://github.com/gonum/gonum. 13 | // 14 | // Package dot implements a parser for Graphviz DOT files. 15 | package dot 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "io/ioutil" 21 | 22 | "github.com/gonum/graph/formats/dot/ast" 23 | "github.com/gonum/graph/formats/dot/internal/lexer" 24 | "github.com/gonum/graph/formats/dot/internal/parser" 25 | ) 26 | 27 | // ParseFile parses the given Graphviz DOT file into an AST. 28 | func ParseFile(path string) (*ast.File, error) { 29 | buf, err := ioutil.ReadFile(path) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return ParseBytes(buf) 34 | } 35 | 36 | // Parse parses the given Graphviz DOT file into an AST, reading from r. 37 | func Parse(r io.Reader) (*ast.File, error) { 38 | buf, err := ioutil.ReadAll(r) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return ParseBytes(buf) 43 | } 44 | 45 | // ParseBytes parses the given Graphviz DOT file into an AST, reading from b. 46 | func ParseBytes(b []byte) (*ast.File, error) { 47 | l := lexer.NewLexer(b) 48 | p := parser.NewParser() 49 | file, err := p.Parse(l) 50 | if err != nil { 51 | return nil, err 52 | } 53 | f, ok := file.(*ast.File) 54 | if !ok { 55 | return nil, fmt.Errorf("invalid file type; expected *ast.File, got %T", file) 56 | } 57 | if err := check(f); err != nil { 58 | return nil, err 59 | } 60 | return f, nil 61 | } 62 | 63 | // ParseString parses the given Graphviz DOT file into an AST, reading from s. 64 | func ParseString(s string) (*ast.File, error) { 65 | return ParseBytes([]byte(s)) 66 | } 67 | -------------------------------------------------------------------------------- /formats/dot/internal/Makefile: -------------------------------------------------------------------------------- 1 | gen: dot.bnf 2 | gocc $< 3 | # TODO: Remove once https://github.com/goccmack/gocc/issues/36 gets resolved. 4 | ./paste_copyright.bash 5 | find . -type f -name '*.go' | xargs goimports -w 6 | 7 | debug_lexer: dot.bnf 8 | gocc -debug_lexer -v -a $< 9 | # TODO: Remove once https://github.com/goccmack/gocc/issues/36 gets resolved. 10 | find . -type f -name '*.go' | xargs goimports -w 11 | 12 | debug_parser: dot.bnf 13 | gocc -debug_parser -v -a $< 14 | # TODO: Remove once https://github.com/goccmack/gocc/issues/36 gets resolved. 15 | find . -type f -name '*.go' | xargs goimports -w 16 | 17 | clean: 18 | rm -f errors/errors.go 19 | rm -f lexer/acttab.go 20 | rm -f lexer/lexer.go 21 | rm -f lexer/transitiontable.go 22 | rm -f parser/action.go 23 | rm -f parser/actiontable.go 24 | rm -f parser/gototable.go 25 | rm -f parser/parser.go 26 | rm -f parser/productionstable.go 27 | rm -f token/token.go 28 | rm -f util/litconv.go 29 | rm -f util/rune.go 30 | -rmdir --ignore-fail-on-non-empty errors 31 | -rmdir --ignore-fail-on-non-empty lexer 32 | -rmdir --ignore-fail-on-non-empty parser 33 | -rmdir --ignore-fail-on-non-empty token 34 | -rmdir --ignore-fail-on-non-empty util 35 | rm -f terminals.txt LR1_conflicts.txt LR1_sets.txt first.txt lexer_sets.txt 36 | 37 | .PHONY: gen debug_lexer debug_parser clean 38 | -------------------------------------------------------------------------------- /formats/dot/internal/astx/astx_test.go: -------------------------------------------------------------------------------- 1 | // This file is dual licensed under CC0 and The gonum license. 2 | // 3 | // Copyright ©2017 The gonum Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | // Copyright ©2017 Robin Eklind. 8 | // This file is made available under a Creative Commons CC0 1.0 9 | // Universal Public Domain Dedication. 10 | 11 | package astx_test 12 | 13 | import ( 14 | "bytes" 15 | "io/ioutil" 16 | "testing" 17 | 18 | "github.com/gonum/graph/formats/dot" 19 | ) 20 | 21 | func TestParseFile(t *testing.T) { 22 | golden := []struct { 23 | in string 24 | out string 25 | }{ 26 | {in: "../testdata/empty.dot"}, 27 | {in: "../testdata/graph.dot"}, 28 | {in: "../testdata/digraph.dot"}, 29 | {in: "../testdata/strict.dot"}, 30 | {in: "../testdata/multi.dot"}, 31 | {in: "../testdata/named_graph.dot"}, 32 | {in: "../testdata/node_stmt.dot"}, 33 | {in: "../testdata/edge_stmt.dot"}, 34 | {in: "../testdata/attr_stmt.dot"}, 35 | {in: "../testdata/attr.dot"}, 36 | { 37 | in: "../testdata/subgraph.dot", 38 | out: "../testdata/subgraph.golden", 39 | }, 40 | { 41 | in: "../testdata/semi.dot", 42 | out: "../testdata/semi.golden", 43 | }, 44 | { 45 | in: "../testdata/empty_attr.dot", 46 | out: "../testdata/empty_attr.golden", 47 | }, 48 | { 49 | in: "../testdata/attr_lists.dot", 50 | out: "../testdata/attr_lists.golden", 51 | }, 52 | { 53 | in: "../testdata/attr_sep.dot", 54 | out: "../testdata/attr_sep.golden", 55 | }, 56 | {in: "../testdata/subgraph_vertex.dot"}, 57 | { 58 | in: "../testdata/port.dot", 59 | out: "../testdata/port.golden", 60 | }, 61 | {in: "../testdata/quoted_id.dot"}, 62 | { 63 | in: "../testdata/backslash_newline_id.dot", 64 | out: "../testdata/backslash_newline_id.golden", 65 | }, 66 | } 67 | for _, g := range golden { 68 | file, err := dot.ParseFile(g.in) 69 | if err != nil { 70 | t.Errorf("%q: unable to parse file; %v", g.in, err) 71 | continue 72 | } 73 | // If no output path is specified, the input is already golden. 74 | out := g.in 75 | if len(g.out) > 0 { 76 | out = g.out 77 | } 78 | buf, err := ioutil.ReadFile(out) 79 | if err != nil { 80 | t.Errorf("%q: unable to read file; %v", g.in, err) 81 | continue 82 | } 83 | got := file.String() 84 | // Remove trailing newline. 85 | want := string(bytes.TrimSpace(buf)) 86 | if got != want { 87 | t.Errorf("%q: graph mismatch; expected `%s`, got `%s`", g.in, want, got) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /formats/dot/internal/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Code generated by gocc; DO NOT EDIT. 2 | 3 | // This file is dual licensed under CC0 and The gonum license. 4 | // 5 | // Copyright ©2017 The gonum Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | // 9 | // Copyright ©2017 Robin Eklind. 10 | // This file is made available under a Creative Commons CC0 1.0 11 | // Universal Public Domain Dedication. 12 | 13 | package errors 14 | 15 | import ( 16 | "bytes" 17 | "fmt" 18 | 19 | "github.com/gonum/graph/formats/dot/internal/token" 20 | ) 21 | 22 | type ErrorSymbol interface { 23 | } 24 | 25 | type Error struct { 26 | Err error 27 | ErrorToken *token.Token 28 | ErrorSymbols []ErrorSymbol 29 | ExpectedTokens []string 30 | StackTop int 31 | } 32 | 33 | func (E *Error) String() string { 34 | w := new(bytes.Buffer) 35 | fmt.Fprintf(w, "Error") 36 | if E.Err != nil { 37 | fmt.Fprintf(w, " %s\n", E.Err) 38 | } else { 39 | fmt.Fprintf(w, "\n") 40 | } 41 | fmt.Fprintf(w, "Token: type=%d, lit=%s\n", E.ErrorToken.Type, E.ErrorToken.Lit) 42 | fmt.Fprintf(w, "Pos: offset=%d, line=%d, column=%d\n", E.ErrorToken.Pos.Offset, E.ErrorToken.Pos.Line, E.ErrorToken.Pos.Column) 43 | fmt.Fprintf(w, "Expected one of: ") 44 | for _, sym := range E.ExpectedTokens { 45 | fmt.Fprintf(w, "%s ", sym) 46 | } 47 | fmt.Fprintf(w, "ErrorSymbol:\n") 48 | for _, sym := range E.ErrorSymbols { 49 | fmt.Fprintf(w, "%v\n", sym) 50 | } 51 | return w.String() 52 | } 53 | 54 | func (e *Error) Error() string { 55 | w := new(bytes.Buffer) 56 | fmt.Fprintf(w, "Error in S%d: %s, %s", e.StackTop, token.TokMap.TokenString(e.ErrorToken), e.ErrorToken.Pos.String()) 57 | if e.Err != nil { 58 | fmt.Fprintf(w, e.Err.Error()) 59 | } else { 60 | fmt.Fprintf(w, ", expected one of: ") 61 | for _, expected := range e.ExpectedTokens { 62 | fmt.Fprintf(w, "%s ", expected) 63 | } 64 | } 65 | return w.String() 66 | } 67 | -------------------------------------------------------------------------------- /formats/dot/internal/lexer/lexer_test.go: -------------------------------------------------------------------------------- 1 | // This file is dual licensed under CC0 and The gonum license. 2 | // 3 | // Copyright ©2017 The gonum Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | // Copyright ©2017 Robin Eklind. 8 | // This file is made available under a Creative Commons CC0 1.0 9 | // Universal Public Domain Dedication. 10 | 11 | package lexer_test 12 | 13 | import ( 14 | "bytes" 15 | "io/ioutil" 16 | "testing" 17 | 18 | "github.com/gonum/graph/formats/dot" 19 | ) 20 | 21 | func TestParseFile(t *testing.T) { 22 | golden := []struct { 23 | in string 24 | out string 25 | }{ 26 | { 27 | in: "testdata/tokens.dot", 28 | out: "testdata/tokens.golden", 29 | }, 30 | } 31 | for _, g := range golden { 32 | file, err := dot.ParseFile(g.in) 33 | if err != nil { 34 | t.Errorf("%q: unable to parse file; %v", g.in, err) 35 | continue 36 | } 37 | // If no output path is specified, the input is already golden. 38 | out := g.in 39 | if len(g.out) > 0 { 40 | out = g.out 41 | } 42 | buf, err := ioutil.ReadFile(out) 43 | if err != nil { 44 | t.Errorf("%q: unable to read file; %v", g.in, err) 45 | continue 46 | } 47 | got := file.String() 48 | // Remove trailing newline. 49 | want := string(bytes.TrimSpace(buf)) 50 | if got != want { 51 | t.Errorf("%q: graph mismatch; expected %q, got %q", g.in, want, got) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /formats/dot/internal/lexer/testdata/tokens.dot: -------------------------------------------------------------------------------- 1 | # C preprocessing directives act as comments. 2 | /* block comment */ 3 | // keywords are case-insensitive. 4 | graph { 5 | node [] 6 | Node [] 7 | NODE [] 8 | edge [] 9 | Edge [] 10 | EDGE [] 11 | subgraph {} 12 | subGraph {} 13 | Subgraph {} 14 | SubGraph {} 15 | SUBGRAPH S {} 16 | A; B [style=filled, fillcolor=red] 17 | C:nw -- D:se 18 | "foo" 19 | .10 20 | -20 21 | 3.14 22 | F [label=<
foo
>] 23 | _foo 24 | a10 25 | } 26 | Graph { 27 | } 28 | GRAPH { 29 | } 30 | digraph { 31 | } 32 | Digraph { 33 | } 34 | diGraph { 35 | } 36 | DiGraph { 37 | } 38 | DIGRAPH { 39 | } 40 | -------------------------------------------------------------------------------- /formats/dot/internal/lexer/testdata/tokens.golden: -------------------------------------------------------------------------------- 1 | graph { 2 | node [] 3 | node [] 4 | node [] 5 | edge [] 6 | edge [] 7 | edge [] 8 | {} 9 | {} 10 | {} 11 | {} 12 | subgraph S {} 13 | A 14 | B [style=filled fillcolor=red] 15 | C:nw -- D:se 16 | "foo" 17 | .10 18 | -20 19 | 3.14 20 | F [label=<
foo
>] 21 | _foo 22 | a10 23 | } 24 | graph { 25 | } 26 | graph { 27 | } 28 | digraph { 29 | } 30 | digraph { 31 | } 32 | digraph { 33 | } 34 | digraph { 35 | } 36 | digraph { 37 | } 38 | -------------------------------------------------------------------------------- /formats/dot/internal/parser/action.go: -------------------------------------------------------------------------------- 1 | // Code generated by gocc; DO NOT EDIT. 2 | 3 | // This file is dual licensed under CC0 and The gonum license. 4 | // 5 | // Copyright ©2017 The gonum Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | // 9 | // Copyright ©2017 Robin Eklind. 10 | // This file is made available under a Creative Commons CC0 1.0 11 | // Universal Public Domain Dedication. 12 | 13 | package parser 14 | 15 | import ( 16 | "fmt" 17 | ) 18 | 19 | type action interface { 20 | act() 21 | String() string 22 | } 23 | 24 | type ( 25 | accept bool 26 | shift int // value is next state index 27 | reduce int // value is production index 28 | ) 29 | 30 | func (this accept) act() {} 31 | func (this shift) act() {} 32 | func (this reduce) act() {} 33 | 34 | func (this accept) Equal(that action) bool { 35 | if _, ok := that.(accept); ok { 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | func (this reduce) Equal(that action) bool { 42 | that1, ok := that.(reduce) 43 | if !ok { 44 | return false 45 | } 46 | return this == that1 47 | } 48 | 49 | func (this shift) Equal(that action) bool { 50 | that1, ok := that.(shift) 51 | if !ok { 52 | return false 53 | } 54 | return this == that1 55 | } 56 | 57 | func (this accept) String() string { return "accept(0)" } 58 | func (this shift) String() string { return fmt.Sprintf("shift:%d", this) } 59 | func (this reduce) String() string { 60 | return fmt.Sprintf("reduce:%d(%s)", this, productionsTable[this].String) 61 | } 62 | -------------------------------------------------------------------------------- /formats/dot/internal/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | // This file is dual licensed under CC0 and The gonum license. 2 | // 3 | // Copyright ©2017 The gonum Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | // Copyright ©2017 Robin Eklind. 8 | // This file is made available under a Creative Commons CC0 1.0 9 | // Universal Public Domain Dedication. 10 | 11 | package parser_test 12 | 13 | import ( 14 | "bytes" 15 | "io/ioutil" 16 | "testing" 17 | 18 | "github.com/gonum/graph/formats/dot" 19 | ) 20 | 21 | func TestParseFile(t *testing.T) { 22 | golden := []struct { 23 | in string 24 | out string 25 | }{ 26 | {in: "../testdata/empty.dot"}, 27 | {in: "../testdata/graph.dot"}, 28 | {in: "../testdata/digraph.dot"}, 29 | {in: "../testdata/strict.dot"}, 30 | {in: "../testdata/multi.dot"}, 31 | {in: "../testdata/named_graph.dot"}, 32 | {in: "../testdata/node_stmt.dot"}, 33 | {in: "../testdata/edge_stmt.dot"}, 34 | {in: "../testdata/attr_stmt.dot"}, 35 | {in: "../testdata/attr.dot"}, 36 | { 37 | in: "../testdata/subgraph.dot", 38 | out: "../testdata/subgraph.golden", 39 | }, 40 | { 41 | in: "../testdata/semi.dot", 42 | out: "../testdata/semi.golden", 43 | }, 44 | { 45 | in: "../testdata/empty_attr.dot", 46 | out: "../testdata/empty_attr.golden", 47 | }, 48 | { 49 | in: "../testdata/attr_lists.dot", 50 | out: "../testdata/attr_lists.golden", 51 | }, 52 | { 53 | in: "../testdata/attr_sep.dot", 54 | out: "../testdata/attr_sep.golden", 55 | }, 56 | {in: "../testdata/subgraph_vertex.dot"}, 57 | { 58 | in: "../testdata/port.dot", 59 | out: "../testdata/port.golden", 60 | }, 61 | {in: "../testdata/quoted_id.dot"}, 62 | { 63 | in: "../testdata/backslash_newline_id.dot", 64 | out: "../testdata/backslash_newline_id.golden", 65 | }, 66 | } 67 | for _, g := range golden { 68 | file, err := dot.ParseFile(g.in) 69 | if err != nil { 70 | t.Errorf("%q: unable to parse file; %v", g.in, err) 71 | continue 72 | } 73 | // If no output path is specified, the input is already golden. 74 | out := g.in 75 | if len(g.out) > 0 { 76 | out = g.out 77 | } 78 | buf, err := ioutil.ReadFile(out) 79 | if err != nil { 80 | t.Errorf("%q: unable to read file; %v", g.in, err) 81 | continue 82 | } 83 | got := file.String() 84 | // Remove trailing newline. 85 | want := string(bytes.TrimSpace(buf)) 86 | if got != want { 87 | t.Errorf("%q: graph mismatch; expected `%s`, got `%s`", g.in, want, got) 88 | } 89 | } 90 | } 91 | 92 | func TestParseError(t *testing.T) { 93 | golden := []struct { 94 | path string 95 | want string 96 | }{ 97 | { 98 | path: "../testdata/error.dot", 99 | want: `Error in S30: INVALID(0,~), Pos(offset=13, line=2, column=7), expected one of: { } graphx ; -- -> node edge [ = subgraph : id `, 100 | }, 101 | } 102 | for _, g := range golden { 103 | _, err := dot.ParseFile(g.path) 104 | if err == nil { 105 | t.Errorf("%q: expected error, got nil", g.path) 106 | continue 107 | } 108 | got := err.Error() 109 | if got != g.want { 110 | t.Errorf("%q: error mismatch; expected `%v`, got `%v`", g.path, g.want, got) 111 | continue 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /formats/dot/internal/paste_copyright.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | find . -type f -name '*.go' \ 4 | | xargs sed -i -e "s|// Code generated by gocc; DO NOT EDIT.|\ 5 | // Code generated by gocc; DO NOT EDIT.\n\ 6 | \n\ 7 | // This file is dual licensed under CC0 and The gonum license.\n\ 8 | //\n\ 9 | // Copyright ©2017 The gonum Authors. All rights reserved.\n\ 10 | // Use of this source code is governed by a BSD-style\n\ 11 | // license that can be found in the LICENSE file.\n\ 12 | //\n\ 13 | // Copyright ©2017 Robin Eklind.\n\ 14 | // This file is made available under a Creative Commons CC0 1.0\n\ 15 | // Universal Public Domain Dedication.\n\ 16 | |" 17 | 18 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/attr.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | bgcolor=transparent 3 | A 4 | } 5 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/attr_lists.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A [style=filled] [fillcolor=red] 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/attr_lists.golden: -------------------------------------------------------------------------------- 1 | digraph { 2 | A [style=filled fillcolor=red] 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/attr_sep.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A [style=filled, fillcolor=red; color=blue] 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/attr_sep.golden: -------------------------------------------------------------------------------- 1 | digraph { 2 | A [style=filled fillcolor=red color=blue] 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/attr_stmt.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | graph [bgcolor=transparent] 3 | node [style=filled fillcolor=white] 4 | edge [minlen=2] 5 | A -> B 6 | } 7 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/backslash_newline_id.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A [name="hello \ 3 | world"] 4 | } 5 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/backslash_newline_id.golden: -------------------------------------------------------------------------------- 1 | digraph { 2 | A [name="hello world"] 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/digraph.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A -> B 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/edge_stmt.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A -> B -> C 3 | D -> E [color=red minlen=2] 4 | } 5 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/empty.dot: -------------------------------------------------------------------------------- 1 | graph { 2 | } 3 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/empty_attr.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A [] 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/empty_attr.golden: -------------------------------------------------------------------------------- 1 | digraph { 2 | A 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/error.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A ~ B 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/graph.dot: -------------------------------------------------------------------------------- 1 | graph { 2 | A -- B 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/multi.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A -> B 3 | } 4 | digraph { 5 | C -> D 6 | } 7 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/named_graph.dot: -------------------------------------------------------------------------------- 1 | graph G { 2 | A 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/node_stmt.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/port.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A:ne -> B:sw 3 | C:foo -> D:bar:se 4 | E:_ -> F 5 | G:n 6 | H:e 7 | I:s 8 | J:w 9 | K:nw 10 | L:c 11 | } 12 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/port.golden: -------------------------------------------------------------------------------- 1 | digraph { 2 | A:ne -> B:sw 3 | C:foo -> D:bar:se 4 | E -> F 5 | G:n 6 | H:e 7 | I:s 8 | J:w 9 | K:nw 10 | L:c 11 | } 12 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/quoted_id.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | "A" -> "B" ["color"="red"] 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/semi.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | A -> B; C 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/semi.golden: -------------------------------------------------------------------------------- 1 | digraph { 2 | A -> B 3 | C 4 | } 5 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/strict.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | A -> B 3 | A -> B 4 | } 5 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/subgraph.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | {A} 3 | subgraph {B} 4 | subgraph S {C} 5 | } 6 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/subgraph.golden: -------------------------------------------------------------------------------- 1 | digraph { 2 | {A} 3 | {B} 4 | subgraph S {C} 5 | } 6 | -------------------------------------------------------------------------------- /formats/dot/internal/testdata/subgraph_vertex.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | {A B} -> C 3 | } 4 | -------------------------------------------------------------------------------- /formats/dot/internal/token/token.go: -------------------------------------------------------------------------------- 1 | // Code generated by gocc; DO NOT EDIT. 2 | 3 | // This file is dual licensed under CC0 and The gonum license. 4 | // 5 | // Copyright ©2017 The gonum Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | // 9 | // Copyright ©2017 Robin Eklind. 10 | // This file is made available under a Creative Commons CC0 1.0 11 | // Universal Public Domain Dedication. 12 | 13 | package token 14 | 15 | import ( 16 | "fmt" 17 | ) 18 | 19 | type Token struct { 20 | Type 21 | Lit []byte 22 | Pos 23 | } 24 | 25 | type Type int 26 | 27 | const ( 28 | INVALID Type = iota 29 | EOF 30 | ) 31 | 32 | type Pos struct { 33 | Offset int 34 | Line int 35 | Column int 36 | } 37 | 38 | func (this Pos) String() string { 39 | return fmt.Sprintf("Pos(offset=%d, line=%d, column=%d)", this.Offset, this.Line, this.Column) 40 | } 41 | 42 | type TokenMap struct { 43 | typeMap []string 44 | idMap map[string]Type 45 | } 46 | 47 | func (this TokenMap) Id(tok Type) string { 48 | if int(tok) < len(this.typeMap) { 49 | return this.typeMap[tok] 50 | } 51 | return "unknown" 52 | } 53 | 54 | func (this TokenMap) Type(tok string) Type { 55 | if typ, exist := this.idMap[tok]; exist { 56 | return typ 57 | } 58 | return INVALID 59 | } 60 | 61 | func (this TokenMap) TokenString(tok *Token) string { 62 | //TODO: refactor to print pos & token string properly 63 | return fmt.Sprintf("%s(%d,%s)", this.Id(tok.Type), tok.Type, tok.Lit) 64 | } 65 | 66 | func (this TokenMap) StringType(typ Type) string { 67 | return fmt.Sprintf("%s(%d)", this.Id(typ), typ) 68 | } 69 | 70 | var TokMap = TokenMap{ 71 | typeMap: []string{ 72 | "INVALID", 73 | "$", 74 | "{", 75 | "}", 76 | "empty", 77 | "strict", 78 | "graphx", 79 | "digraph", 80 | ";", 81 | "--", 82 | "->", 83 | "node", 84 | "edge", 85 | "[", 86 | "]", 87 | ",", 88 | "=", 89 | "subgraph", 90 | ":", 91 | "id", 92 | }, 93 | 94 | idMap: map[string]Type{ 95 | "INVALID": 0, 96 | "$": 1, 97 | "{": 2, 98 | "}": 3, 99 | "empty": 4, 100 | "strict": 5, 101 | "graphx": 6, 102 | "digraph": 7, 103 | ";": 8, 104 | "--": 9, 105 | "->": 10, 106 | "node": 11, 107 | "edge": 12, 108 | "[": 13, 109 | "]": 14, 110 | ",": 15, 111 | "=": 16, 112 | "subgraph": 17, 113 | ":": 18, 114 | "id": 19, 115 | }, 116 | } 117 | -------------------------------------------------------------------------------- /formats/dot/internal/util/litconv.go: -------------------------------------------------------------------------------- 1 | // Code generated by gocc; DO NOT EDIT. 2 | 3 | // This file is dual licensed under CC0 and The gonum license. 4 | // 5 | // Copyright ©2017 The gonum Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | // 9 | // Copyright ©2017 Robin Eklind. 10 | // This file is made available under a Creative Commons CC0 1.0 11 | // Universal Public Domain Dedication. 12 | 13 | package util 14 | 15 | import ( 16 | "fmt" 17 | "strconv" 18 | "unicode" 19 | "unicode/utf8" 20 | ) 21 | 22 | /* Interface */ 23 | 24 | /* 25 | Convert the literal value of a scanned token to rune 26 | */ 27 | func RuneValue(lit []byte) rune { 28 | if lit[1] == '\\' { 29 | return escapeCharVal(lit) 30 | } 31 | r, size := utf8.DecodeRune(lit[1:]) 32 | if size != len(lit)-2 { 33 | panic(fmt.Sprintf("Error decoding rune. Lit: %s, rune: %d, size%d\n", lit, r, size)) 34 | } 35 | return r 36 | } 37 | 38 | /* 39 | Convert the literal value of a scanned token to int64 40 | */ 41 | func IntValue(lit []byte) (int64, error) { 42 | return strconv.ParseInt(string(lit), 10, 64) 43 | } 44 | 45 | /* 46 | Convert the literal value of a scanned token to uint64 47 | */ 48 | func UintValue(lit []byte) (uint64, error) { 49 | return strconv.ParseUint(string(lit), 10, 64) 50 | } 51 | 52 | /* Util */ 53 | 54 | func escapeCharVal(lit []byte) rune { 55 | var i, base, max uint32 56 | offset := 2 57 | switch lit[offset] { 58 | case 'a': 59 | return '\a' 60 | case 'b': 61 | return '\b' 62 | case 'f': 63 | return '\f' 64 | case 'n': 65 | return '\n' 66 | case 'r': 67 | return '\r' 68 | case 't': 69 | return '\t' 70 | case 'v': 71 | return '\v' 72 | case '\\': 73 | return '\\' 74 | case '\'': 75 | return '\'' 76 | case '0', '1', '2', '3', '4', '5', '6', '7': 77 | i, base, max = 3, 8, 255 78 | case 'x': 79 | i, base, max = 2, 16, 255 80 | offset++ 81 | case 'u': 82 | i, base, max = 4, 16, unicode.MaxRune 83 | offset++ 84 | case 'U': 85 | i, base, max = 8, 16, unicode.MaxRune 86 | offset++ 87 | default: 88 | panic(fmt.Sprintf("Error decoding character literal: %s\n", lit)) 89 | } 90 | 91 | var x uint32 92 | for ; i > 0 && offset < len(lit)-1; i-- { 93 | ch, size := utf8.DecodeRune(lit[offset:]) 94 | offset += size 95 | d := uint32(digitVal(ch)) 96 | if d >= base { 97 | panic(fmt.Sprintf("charVal(%s): illegal character (%c) in escape sequence. size=%d, offset=%d", lit, ch, size, offset)) 98 | } 99 | x = x*base + d 100 | } 101 | if x > max || 0xD800 <= x && x < 0xE000 { 102 | panic(fmt.Sprintf("Error decoding escape char value. Lit:%s, offset:%d, escape sequence is invalid Unicode code point\n", lit, offset)) 103 | } 104 | 105 | return rune(x) 106 | } 107 | 108 | func digitVal(ch rune) int { 109 | switch { 110 | case '0' <= ch && ch <= '9': 111 | return int(ch) - '0' 112 | case 'a' <= ch && ch <= 'f': 113 | return int(ch) - 'a' + 10 114 | case 'A' <= ch && ch <= 'F': 115 | return int(ch) - 'A' + 10 116 | } 117 | return 16 // larger than any legal digit val 118 | } 119 | -------------------------------------------------------------------------------- /formats/dot/internal/util/rune.go: -------------------------------------------------------------------------------- 1 | // Code generated by gocc; DO NOT EDIT. 2 | 3 | // This file is dual licensed under CC0 and The gonum license. 4 | // 5 | // Copyright ©2017 The gonum Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | // 9 | // Copyright ©2017 Robin Eklind. 10 | // This file is made available under a Creative Commons CC0 1.0 11 | // Universal Public Domain Dedication. 12 | 13 | package util 14 | 15 | import ( 16 | "fmt" 17 | ) 18 | 19 | func RuneToString(r rune) string { 20 | if r >= 0x20 && r < 0x7f { 21 | return fmt.Sprintf("'%c'", r) 22 | } 23 | switch r { 24 | case 0x07: 25 | return "'\\a'" 26 | case 0x08: 27 | return "'\\b'" 28 | case 0x0C: 29 | return "'\\f'" 30 | case 0x0A: 31 | return "'\\n'" 32 | case 0x0D: 33 | return "'\\r'" 34 | case 0x09: 35 | return "'\\t'" 36 | case 0x0b: 37 | return "'\\v'" 38 | case 0x5c: 39 | return "'\\\\\\'" 40 | case 0x27: 41 | return "'\\''" 42 | case 0x22: 43 | return "'\\\"'" 44 | } 45 | if r < 0x10000 { 46 | return fmt.Sprintf("\\u%04x", r) 47 | } 48 | return fmt.Sprintf("\\U%08x", r) 49 | } 50 | -------------------------------------------------------------------------------- /formats/dot/sem.go: -------------------------------------------------------------------------------- 1 | // This file is dual licensed under CC0 and The gonum license. 2 | // 3 | // Copyright ©2017 The gonum Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | // Copyright ©2017 Robin Eklind. 8 | // This file is made available under a Creative Commons CC0 1.0 9 | // Universal Public Domain Dedication. 10 | 11 | package dot 12 | 13 | import ( 14 | "fmt" 15 | 16 | "github.com/gonum/graph/formats/dot/ast" 17 | ) 18 | 19 | // check validates the semantics of the given DOT file. 20 | func check(file *ast.File) error { 21 | for _, graph := range file.Graphs { 22 | // TODO: Check graph.ID for duplicates? 23 | if err := checkGraph(graph); err != nil { 24 | return err 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | // check validates the semantics of the given graph. 31 | func checkGraph(graph *ast.Graph) error { 32 | for _, stmt := range graph.Stmts { 33 | if err := checkStmt(graph, stmt); err != nil { 34 | return err 35 | } 36 | } 37 | return nil 38 | } 39 | 40 | // check validates the semantics of the given statement. 41 | func checkStmt(graph *ast.Graph, stmt ast.Stmt) error { 42 | switch stmt := stmt.(type) { 43 | case *ast.NodeStmt: 44 | return checkNodeStmt(graph, stmt) 45 | case *ast.EdgeStmt: 46 | return checkEdgeStmt(graph, stmt) 47 | case *ast.AttrStmt: 48 | return checkAttrStmt(graph, stmt) 49 | case *ast.Attr: 50 | // TODO: Verify that the attribute is indeed of graph component kind. 51 | return checkAttr(graph, ast.KindGraph, stmt) 52 | case *ast.Subgraph: 53 | return checkSubgraph(graph, stmt) 54 | default: 55 | panic(fmt.Sprintf("support for statement of type %T not yet implemented", stmt)) 56 | } 57 | } 58 | 59 | // checkNodeStmt validates the semantics of the given node statement. 60 | func checkNodeStmt(graph *ast.Graph, stmt *ast.NodeStmt) error { 61 | if err := checkNode(graph, stmt.Node); err != nil { 62 | return err 63 | } 64 | for _, attr := range stmt.Attrs { 65 | // TODO: Verify that the attribute is indeed of node component kind. 66 | if err := checkAttr(graph, ast.KindNode, attr); err != nil { 67 | return err 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | // checkEdgeStmt validates the semantics of the given edge statement. 74 | func checkEdgeStmt(graph *ast.Graph, stmt *ast.EdgeStmt) error { 75 | // TODO: if graph.Strict, check for multi-edges. 76 | if err := checkVertex(graph, stmt.From); err != nil { 77 | return err 78 | } 79 | for _, attr := range stmt.Attrs { 80 | // TODO: Verify that the attribute is indeed of edge component kind. 81 | if err := checkAttr(graph, ast.KindEdge, attr); err != nil { 82 | return err 83 | } 84 | } 85 | return checkEdge(graph, stmt.From, stmt.To) 86 | } 87 | 88 | // checkEdge validates the semantics of the given edge. 89 | func checkEdge(graph *ast.Graph, from ast.Vertex, to *ast.Edge) error { 90 | if !graph.Directed && to.Directed { 91 | return fmt.Errorf("undirected graph %q contains directed edge from %q to %q", graph.ID, from, to.Vertex) 92 | } 93 | if err := checkVertex(graph, to.Vertex); err != nil { 94 | return err 95 | } 96 | if to.To != nil { 97 | return checkEdge(graph, to.Vertex, to.To) 98 | } 99 | return nil 100 | } 101 | 102 | // checkAttrStmt validates the semantics of the given attribute statement. 103 | func checkAttrStmt(graph *ast.Graph, stmt *ast.AttrStmt) error { 104 | for _, attr := range stmt.Attrs { 105 | if err := checkAttr(graph, stmt.Kind, attr); err != nil { 106 | return err 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | // checkAttr validates the semantics of the given attribute for the given 113 | // component kind. 114 | func checkAttr(graph *ast.Graph, kind ast.Kind, attr *ast.Attr) error { 115 | switch kind { 116 | case ast.KindGraph: 117 | // TODO: Validate key-value pairs for graphs. 118 | return nil 119 | case ast.KindNode: 120 | // TODO: Validate key-value pairs for nodes. 121 | return nil 122 | case ast.KindEdge: 123 | // TODO: Validate key-value pairs for edges. 124 | return nil 125 | default: 126 | panic(fmt.Sprintf("support for component kind %v not yet supported", kind)) 127 | } 128 | } 129 | 130 | // checkSubgraph validates the semantics of the given subgraph. 131 | func checkSubgraph(graph *ast.Graph, subgraph *ast.Subgraph) error { 132 | // TODO: Check subgraph.ID for duplicates? 133 | for _, stmt := range subgraph.Stmts { 134 | // TODO: Refine handling of subgraph statements? 135 | // checkSubgraphStmt(graph, subgraph, stmt) 136 | if err := checkStmt(graph, stmt); err != nil { 137 | return err 138 | } 139 | } 140 | return nil 141 | } 142 | 143 | // checkVertex validates the semantics of the given vertex. 144 | func checkVertex(graph *ast.Graph, vertex ast.Vertex) error { 145 | switch vertex := vertex.(type) { 146 | case *ast.Node: 147 | return checkNode(graph, vertex) 148 | case *ast.Subgraph: 149 | return checkSubgraph(graph, vertex) 150 | default: 151 | panic(fmt.Sprintf("support for vertex of type %T not yet supported", vertex)) 152 | } 153 | } 154 | 155 | // checNode validates the semantics of the given node. 156 | func checkNode(graph *ast.Graph, node *ast.Node) error { 157 | // TODO: Check node.ID for duplicates? 158 | // TODO: Validate node.Port. 159 | return nil 160 | } 161 | -------------------------------------------------------------------------------- /formats/dot/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | *.dot 2 | *.png 3 | graphviz 4 | input 5 | output 6 | -------------------------------------------------------------------------------- /formats/dot/testdata/Makefile: -------------------------------------------------------------------------------- 1 | # Dependencies: 2 | # 3 | # * imgcmp 4 | # go get github.com/mewkiz/cmd/imgcmp 5 | # * dotfmt 6 | # go get github.com/graphism/dot/cmd/dotfmt 7 | # * dot 8 | # sudo pacman -S graphviz 9 | # * recode 10 | # sudo pacman -S recode 11 | 12 | DOT=$(wildcard *.dot) 13 | 14 | # Skip DOT files for which the generated PNG images mismatch. 15 | # 16 | # ref: https://github.com/graphism/dot/issues/2 17 | # 18 | # pixel colors differ at x=550, y=1885 19 | DOT:=$(filter-out b51.dot, $(DOT)) 20 | # pixel colors differ at x=5395, y=1920 21 | DOT:=$(filter-out b106.dot, $(DOT)) 22 | 23 | # Skip segfaulting files. 24 | # 25 | # Segmentation fault (core dumped) 26 | DOT:=$(filter-out b15.dot, $(DOT)) 27 | # Segmentation fault (core dumped) 28 | DOT:=$(filter-out b81.dot, $(DOT)) 29 | # *** stack smashing detected ***: dot terminated 30 | DOT:=$(filter-out sides.dot, $(DOT)) 31 | # *** stack smashing detected ***: dot terminated 32 | DOT:=$(filter-out tee.dot, $(DOT)) 33 | 34 | # Skip DOT files above 100 kB. 35 | DOT:=$(filter-out 4elt.dot, $(DOT)) 36 | DOT:=$(filter-out b29.dot, $(DOT)) 37 | DOT:=$(filter-out b81.dot, $(DOT)) 38 | DOT:=$(filter-out b100.dot, $(DOT)) 39 | DOT:=$(filter-out b102.dot, $(DOT)) 40 | DOT:=$(filter-out b103.dot, $(DOT)) 41 | DOT:=$(filter-out b104.dot, $(DOT)) 42 | DOT:=$(filter-out root.dot, $(DOT)) 43 | DOT:=$(filter-out root_circo.dot, $(DOT)) 44 | DOT:=$(filter-out root_twopi.dot, $(DOT)) 45 | 46 | # Skip invalid DOT file. 47 | # 48 | # Error: No or improper image file="eqn.png" 49 | # in label of node struct1 50 | DOT:=$(filter-out html4.dot, $(DOT)) 51 | 52 | # Skip multi-graph DOT file which outputs to standard output. 53 | DOT:=$(filter-out multi.dot, $(DOT)) 54 | 55 | # *.dot -> *.png 56 | PNG=$(DOT:.dot=.png) 57 | 58 | INPUT_PNG=$(addprefix input/,$(PNG)) 59 | OUTPUT_PNG=$(addprefix output/,$(PNG)) 60 | 61 | all: 62 | 63 | test: input $(INPUT_PNG) output $(OUTPUT_PNG) 64 | @echo "PASS" 65 | 66 | input: 67 | mkdir -p $@ 68 | dot -V 69 | 70 | input/%.png: %.dot 71 | dot -Tpng -o $@ $< 72 | 73 | output: 74 | mkdir -p $@ 75 | 76 | output/%.png: %.dot 77 | dotfmt -o "output/$<" $< 78 | dot -Tpng -o $@ "output/$<" 79 | imgcmp "input/$(notdir $@)" $@ 80 | 81 | fetch: graphviz 82 | # Copy *.gv and *.dot files. 83 | find graphviz -type f -name '*.gv' -not -wholename "graphviz/rtest/share/b545.gv" -not -name "base.gv" | xargs -I '{}' cp "{}" . 84 | find graphviz -type f -name '*.dot' | xargs -I '{}' cp "{}" . 85 | 86 | # Rename *.gv to *.dot. 87 | #rename .gv .dot *.gv 88 | ls *.gv | xargs -I '{}' basename "{}" .gv | xargs -I '{}' mv "{}.gv" "{}.dot" 89 | 90 | # Remove execute permissions. 91 | chmod 0644 *.dot 92 | 93 | # Convert Latin1 encoded files to UTF-8. 94 | grep -l "charset=latin1" *.dot | xargs -I '{}' recode ISO-8859-1..UTF8 "{}" 95 | recode ISO-8859-1..UTF8 Latin1.dot 96 | 97 | # Clean up. 98 | rm -rf graphviz 99 | 100 | graphviz: 101 | git clone https://github.com/ellson/graphviz.git 102 | 103 | clean: 104 | rm -rf *.dot input output 105 | 106 | .PHONY: all test fetch clean 107 | -------------------------------------------------------------------------------- /graph.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package graph 6 | 7 | // Node is a graph node. It returns a graph-unique integer ID. 8 | type Node interface { 9 | ID() int 10 | } 11 | 12 | // Edge is a graph edge. In directed graphs, the direction of the 13 | // edge is given from -> to, otherwise the edge is semantically 14 | // unordered. 15 | type Edge interface { 16 | From() Node 17 | To() Node 18 | Weight() float64 19 | } 20 | 21 | // Graph is a generalized graph. 22 | type Graph interface { 23 | // Has returns whether the node exists within the graph. 24 | Has(Node) bool 25 | 26 | // Nodes returns all the nodes in the graph. 27 | Nodes() []Node 28 | 29 | // From returns all nodes that can be reached directly 30 | // from the given node. 31 | From(Node) []Node 32 | 33 | // HasEdgeBeteen returns whether an edge exists between 34 | // nodes x and y without considering direction. 35 | HasEdgeBetween(x, y Node) bool 36 | 37 | // Edge returns the edge from u to v if such an edge 38 | // exists and nil otherwise. The node v must be directly 39 | // reachable from u as defined by the From method. 40 | Edge(u, v Node) Edge 41 | } 42 | 43 | // Undirected is an undirected graph. 44 | type Undirected interface { 45 | Graph 46 | 47 | // EdgeBetween returns the edge between nodes x and y. 48 | EdgeBetween(x, y Node) Edge 49 | } 50 | 51 | // Directed is a directed graph. 52 | type Directed interface { 53 | Graph 54 | 55 | // HasEdgeFromTo returns whether an edge exists 56 | // in the graph from u to v. 57 | HasEdgeFromTo(u, v Node) bool 58 | 59 | // To returns all nodes that can reach directly 60 | // to the given node. 61 | To(Node) []Node 62 | } 63 | 64 | // Weighter defines graphs that can report edge weights. 65 | type Weighter interface { 66 | // Weight returns the weight for the edge between 67 | // x and y if Edge(x, y) returns a non-nil Edge. 68 | // If x and y are the same node or there is no 69 | // joining edge between the two nodes the weight 70 | // value returned is implementation dependent. 71 | // Weight returns true if an edge exists between 72 | // x and y or if x and y have the same ID, false 73 | // otherwise. 74 | Weight(x, y Node) (w float64, ok bool) 75 | } 76 | 77 | // NodeAdder is an interface for adding arbitrary nodes to a graph. 78 | type NodeAdder interface { 79 | // NewNodeID returns a new unique arbitrary ID. 80 | NewNodeID() int 81 | 82 | // Adds a node to the graph. AddNode panics if 83 | // the added node ID matches an existing node ID. 84 | AddNode(Node) 85 | } 86 | 87 | // NodeRemover is an interface for removing nodes from a graph. 88 | type NodeRemover interface { 89 | // RemoveNode removes a node from the graph, as 90 | // well as any edges attached to it. If the node 91 | // is not in the graph it is a no-op. 92 | RemoveNode(Node) 93 | } 94 | 95 | // EdgeSetter is an interface for adding edges to a graph. 96 | type EdgeSetter interface { 97 | // SetEdge adds an edge from one node to another. 98 | // If the graph supports node addition the nodes 99 | // will be added if they do not exist, otherwise 100 | // SetEdge will panic. 101 | // If the IDs returned by e.From and e.To are 102 | // equal, SetEdge will panic. 103 | SetEdge(e Edge) 104 | } 105 | 106 | // EdgeRemover is an interface for removing nodes from a graph. 107 | type EdgeRemover interface { 108 | // RemoveEdge removes the given edge, leaving the 109 | // terminal nodes. If the edge does not exist it 110 | // is a no-op. 111 | RemoveEdge(Edge) 112 | } 113 | 114 | // Builder is a graph that can have nodes and edges added. 115 | type Builder interface { 116 | NodeAdder 117 | EdgeSetter 118 | } 119 | 120 | // UndirectedBuilder is an undirected graph builder. 121 | type UndirectedBuilder interface { 122 | Undirected 123 | Builder 124 | } 125 | 126 | // DirectedBuilder is a directed graph builder. 127 | type DirectedBuilder interface { 128 | Directed 129 | Builder 130 | } 131 | 132 | // Copy copies nodes and edges as undirected edges from the source to the destination 133 | // without first clearing the destination. Copy will panic if a node ID in the source 134 | // graph matches a node ID in the destination. 135 | // 136 | // If the source is undirected and the destination is directed both directions will 137 | // be present in the destination after the copy is complete. 138 | // 139 | // If the source is a directed graph, the destination is undirected, and a fundamental 140 | // cycle exists with two nodes where the edge weights differ, the resulting destination 141 | // graph's edge weight between those nodes is undefined. If there is a defined function 142 | // to resolve such conflicts, an Undirect may be used to do this. 143 | func Copy(dst Builder, src Graph) { 144 | nodes := src.Nodes() 145 | for _, n := range nodes { 146 | dst.AddNode(n) 147 | } 148 | for _, u := range nodes { 149 | for _, v := range src.From(u) { 150 | dst.SetEdge(src.Edge(u, v)) 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /graphs/gen/batagelj_brandes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | "github.com/gonum/graph/simple" 13 | ) 14 | 15 | type gnUndirected struct { 16 | graph.UndirectedBuilder 17 | addBackwards bool 18 | addSelfLoop bool 19 | addMultipleEdge bool 20 | } 21 | 22 | func (g *gnUndirected) SetEdge(e graph.Edge) { 23 | switch { 24 | case e.From().ID() == e.To().ID(): 25 | g.addSelfLoop = true 26 | return 27 | case e.From().ID() > e.To().ID(): 28 | g.addBackwards = true 29 | case g.UndirectedBuilder.HasEdgeBetween(e.From(), e.To()): 30 | g.addMultipleEdge = true 31 | } 32 | 33 | g.UndirectedBuilder.SetEdge(e) 34 | } 35 | 36 | type gnDirected struct { 37 | graph.DirectedBuilder 38 | addSelfLoop bool 39 | addMultipleEdge bool 40 | } 41 | 42 | func (g *gnDirected) SetEdge(e graph.Edge) { 43 | switch { 44 | case e.From().ID() == e.To().ID(): 45 | g.addSelfLoop = true 46 | return 47 | case g.DirectedBuilder.HasEdgeFromTo(e.From(), e.To()): 48 | g.addMultipleEdge = true 49 | } 50 | 51 | g.DirectedBuilder.SetEdge(e) 52 | } 53 | 54 | func TestGnpUndirected(t *testing.T) { 55 | for n := 2; n <= 20; n++ { 56 | for p := 0.; p <= 1; p += 0.1 { 57 | g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} 58 | err := Gnp(g, n, p, nil) 59 | if err != nil { 60 | t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) 61 | } 62 | if g.addBackwards { 63 | t.Errorf("edge added with From.ID > To.ID: n=%d, p=%v", n, p) 64 | } 65 | if g.addSelfLoop { 66 | t.Errorf("unexpected self edge: n=%d, p=%v", n, p) 67 | } 68 | if g.addMultipleEdge { 69 | t.Errorf("unexpected multiple edge: n=%d, p=%v", n, p) 70 | } 71 | } 72 | } 73 | } 74 | 75 | func TestGnpDirected(t *testing.T) { 76 | for n := 2; n <= 20; n++ { 77 | for p := 0.; p <= 1; p += 0.1 { 78 | g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} 79 | err := Gnp(g, n, p, nil) 80 | if err != nil { 81 | t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) 82 | } 83 | if g.addSelfLoop { 84 | t.Errorf("unexpected self edge: n=%d, p=%v", n, p) 85 | } 86 | if g.addMultipleEdge { 87 | t.Errorf("unexpected multiple edge: n=%d, p=%v", n, p) 88 | } 89 | } 90 | } 91 | } 92 | 93 | func TestGnmUndirected(t *testing.T) { 94 | for n := 2; n <= 20; n++ { 95 | nChoose2 := (n - 1) * n / 2 96 | for m := 0; m <= nChoose2; m++ { 97 | g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} 98 | err := Gnm(g, n, m, nil) 99 | if err != nil { 100 | t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) 101 | } 102 | if g.addBackwards { 103 | t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d", n, m) 104 | } 105 | if g.addSelfLoop { 106 | t.Errorf("unexpected self edge: n=%d, m=%d", n, m) 107 | } 108 | if g.addMultipleEdge { 109 | t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) 110 | } 111 | } 112 | } 113 | } 114 | 115 | func TestGnmDirected(t *testing.T) { 116 | for n := 2; n <= 20; n++ { 117 | nChoose2 := (n - 1) * n / 2 118 | for m := 0; m <= nChoose2*2; m++ { 119 | g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} 120 | err := Gnm(g, n, m, nil) 121 | if err != nil { 122 | t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) 123 | } 124 | if g.addSelfLoop { 125 | t.Errorf("unexpected self edge: n=%d, m=%d", n, m) 126 | } 127 | if g.addMultipleEdge { 128 | t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) 129 | } 130 | } 131 | } 132 | } 133 | 134 | func TestSmallWorldsBBUndirected(t *testing.T) { 135 | for n := 2; n <= 20; n++ { 136 | for d := 1; d <= (n-1)/2; d++ { 137 | for p := 0.; p < 1; p += 0.1 { 138 | g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} 139 | err := SmallWorldsBB(g, n, d, p, nil) 140 | if err != nil { 141 | t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) 142 | } 143 | if g.addBackwards { 144 | t.Errorf("edge added with From.ID > To.ID: n=%d, d=%d, p=%v", n, d, p) 145 | } 146 | if g.addSelfLoop { 147 | t.Errorf("unexpected self edge: n=%d, d=%d, p=%v", n, d, p) 148 | } 149 | if g.addMultipleEdge { 150 | t.Errorf("unexpected multiple edge: n=%d, d=%d, p=%v", n, d, p) 151 | } 152 | } 153 | } 154 | } 155 | } 156 | 157 | func TestSmallWorldsBBDirected(t *testing.T) { 158 | for n := 2; n <= 20; n++ { 159 | for d := 1; d <= (n-1)/2; d++ { 160 | for p := 0.; p < 1; p += 0.1 { 161 | g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} 162 | err := SmallWorldsBB(g, n, d, p, nil) 163 | if err != nil { 164 | t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) 165 | } 166 | if g.addSelfLoop { 167 | t.Errorf("unexpected self edge: n=%d, d=%d, p=%v", n, d, p) 168 | } 169 | if g.addMultipleEdge { 170 | t.Errorf("unexpected multiple edge: n=%d, d=%d, p=%v", n, d, p) 171 | } 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /graphs/gen/duplication.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "math/rand" 11 | "sort" 12 | 13 | "github.com/gonum/graph" 14 | "github.com/gonum/graph/internal/ordered" 15 | "github.com/gonum/graph/simple" 16 | ) 17 | 18 | // UndirectedMutator is an undirected graph builder that can remove edges. 19 | type UndirectedMutator interface { 20 | graph.UndirectedBuilder 21 | graph.EdgeRemover 22 | } 23 | 24 | // Duplication constructs a graph in the destination, dst, of order n. New nodes 25 | // are created by duplicating an existing node and all its edges. Each new edge is 26 | // deleted with probability delta. Additional edges are added between the new node 27 | // and existing nodes with probability alpha/|V|. An exception to this addition 28 | // rule is made for the parent node when sigma is not NaN; in this case an edge is 29 | // created with probability sigma. With the exception of the sigma parameter, this 30 | // corresponds to the completely correlated case in doi:10.1016/S0022-5193(03)00028-6. 31 | // If src is not nil it is used as the random source, otherwise rand.Float64 is used. 32 | func Duplication(dst UndirectedMutator, n int, delta, alpha, sigma float64, src *rand.Rand) error { 33 | // As described in doi:10.1016/S0022-5193(03)00028-6 but 34 | // also clarified in doi:10.1186/gb-2007-8-4-r51. 35 | 36 | if delta < 0 || delta > 1 { 37 | return fmt.Errorf("gen: bad delta: delta=%v", delta) 38 | } 39 | if alpha <= 0 || alpha > 1 { 40 | return fmt.Errorf("gen: bad alpha: alpha=%v", alpha) 41 | } 42 | if sigma < 0 || sigma > 1 { 43 | return fmt.Errorf("gen: bad sigma: sigma=%v", sigma) 44 | } 45 | 46 | var ( 47 | rnd func() float64 48 | rndN func(int) int 49 | ) 50 | if src == nil { 51 | rnd = rand.Float64 52 | rndN = rand.Intn 53 | } else { 54 | rnd = src.Float64 55 | rndN = src.Intn 56 | } 57 | 58 | nodes := dst.Nodes() 59 | sort.Sort(ordered.ByID(nodes)) 60 | if len(nodes) == 0 { 61 | n-- 62 | dst.AddNode(simple.Node(0)) 63 | nodes = append(nodes, simple.Node(0)) 64 | } 65 | for i := 0; i < n; i++ { 66 | u := nodes[rndN(len(nodes))] 67 | d := simple.Node(dst.NewNodeID()) 68 | 69 | // Add the duplicate node. 70 | dst.AddNode(d) 71 | 72 | // Loop until we have connectivity 73 | // into the rest of the graph. 74 | for { 75 | // Add edges to parent's neigbours. 76 | to := dst.From(u) 77 | sort.Sort(ordered.ByID(to)) 78 | for _, v := range to { 79 | if rnd() < delta || dst.HasEdgeBetween(v, d) { 80 | continue 81 | } 82 | if v.ID() < d.ID() { 83 | dst.SetEdge(simple.Edge{F: v, T: d, W: 1}) 84 | } else { 85 | dst.SetEdge(simple.Edge{F: d, T: v, W: 1}) 86 | } 87 | } 88 | 89 | // Add edges to old nodes. 90 | scaledAlpha := alpha / float64(len(nodes)) 91 | for _, v := range nodes { 92 | switch v.ID() { 93 | case u.ID(): 94 | if !math.IsNaN(sigma) { 95 | if i == 0 || rnd() < sigma { 96 | if v.ID() < d.ID() { 97 | dst.SetEdge(simple.Edge{F: v, T: d, W: 1}) 98 | } else { 99 | dst.SetEdge(simple.Edge{F: d, T: v, W: 1}) 100 | } 101 | } 102 | continue 103 | } 104 | fallthrough 105 | default: 106 | if rnd() < scaledAlpha && !dst.HasEdgeBetween(v, d) { 107 | if v.ID() < d.ID() { 108 | dst.SetEdge(simple.Edge{F: v, T: d, W: 1}) 109 | } else { 110 | dst.SetEdge(simple.Edge{F: d, T: v, W: 1}) 111 | } 112 | } 113 | } 114 | } 115 | 116 | if len(dst.From(d)) != 0 { 117 | break 118 | } 119 | } 120 | 121 | nodes = append(nodes, d) 122 | } 123 | 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /graphs/gen/duplication_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | "github.com/gonum/graph/simple" 13 | ) 14 | 15 | type duplication struct { 16 | UndirectedMutator 17 | addBackwards bool 18 | addSelfLoop bool 19 | addMultipleEdge bool 20 | } 21 | 22 | func (g *duplication) SetEdge(e graph.Edge) { 23 | switch { 24 | case e.From().ID() == e.To().ID(): 25 | g.addSelfLoop = true 26 | return 27 | case e.From().ID() > e.To().ID(): 28 | g.addBackwards = true 29 | case g.UndirectedMutator.HasEdgeBetween(e.From(), e.To()): 30 | g.addMultipleEdge = true 31 | } 32 | 33 | g.UndirectedMutator.SetEdge(e) 34 | } 35 | 36 | func TestDuplication(t *testing.T) { 37 | for n := 2; n <= 50; n++ { 38 | for alpha := 0.1; alpha <= 1; alpha += 0.1 { 39 | for delta := 0.; delta <= 1; delta += 0.2 { 40 | for sigma := 0.; sigma <= 1; sigma += 0.2 { 41 | g := &duplication{UndirectedMutator: simple.NewUndirectedGraph(0, math.Inf(1))} 42 | err := Duplication(g, n, delta, alpha, sigma, nil) 43 | if err != nil { 44 | t.Fatalf("unexpected error: n=%d, alpha=%v, delta=%v sigma=%v: %v", n, alpha, delta, sigma, err) 45 | } 46 | if g.addBackwards { 47 | t.Errorf("edge added with From.ID > To.ID: n=%d, alpha=%v, delta=%v sigma=%v", n, alpha, delta, sigma) 48 | } 49 | if g.addSelfLoop { 50 | t.Errorf("unexpected self edge: n=%d, alpha=%v, delta=%v sigma=%v", n, alpha, delta, sigma) 51 | } 52 | if g.addMultipleEdge { 53 | t.Errorf("unexpected multiple edge: n=%d, alpha=%v, delta=%v sigma=%v", n, alpha, delta, sigma) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /graphs/gen/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package gen provides random graph generation functions. 9 | package gen 10 | 11 | import "github.com/gonum/graph" 12 | 13 | // GraphBuilder is a graph that can have nodes and edges added. 14 | type GraphBuilder interface { 15 | Has(graph.Node) bool 16 | HasEdgeBetween(x, y graph.Node) bool 17 | graph.Builder 18 | } 19 | 20 | func abs(a int) int { 21 | if a < 0 { 22 | return -a 23 | } 24 | return a 25 | } 26 | -------------------------------------------------------------------------------- /graphs/gen/holme_kim.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "math/rand" 11 | 12 | "github.com/gonum/graph" 13 | "github.com/gonum/graph/simple" 14 | "github.com/gonum/stat/sampleuv" 15 | ) 16 | 17 | // TunableClusteringScaleFree constructs a graph in the destination, dst, of order n. 18 | // The graph is constructed successively starting from an m order graph with one node 19 | // having degree m-1. At each iteration of graph addition, one node is added with m 20 | // additional edges joining existing nodes with probability proportional to the nodes' 21 | // degrees. The edges are formed as a triad with probability, p. 22 | // If src is not nil it is used as the random source, otherwise rand.Float64 and 23 | // rand.Intn are used. 24 | // 25 | // The algorithm is essentially as described in http://arxiv.org/abs/cond-mat/0110452. 26 | func TunableClusteringScaleFree(dst graph.UndirectedBuilder, n, m int, p float64, src *rand.Rand) error { 27 | if p < 0 || p > 1 { 28 | return fmt.Errorf("gen: bad probability: p=%v", p) 29 | } 30 | if n <= m { 31 | return fmt.Errorf("gen: n <= m: n=%v m=%d", n, m) 32 | } 33 | 34 | var ( 35 | rnd func() float64 36 | rndN func(int) int 37 | ) 38 | if src == nil { 39 | rnd = rand.Float64 40 | rndN = rand.Intn 41 | } else { 42 | rnd = src.Float64 43 | rndN = src.Intn 44 | } 45 | 46 | // Initial condition. 47 | wt := make([]float64, n) 48 | for u := 0; u < m; u++ { 49 | if !dst.Has(simple.Node(u)) { 50 | dst.AddNode(simple.Node(u)) 51 | } 52 | // We need to give equal probability for 53 | // adding the first generation of edges. 54 | wt[u] = 1 55 | } 56 | ws := sampleuv.NewWeighted(wt, src) 57 | for i := range wt { 58 | // These weights will organically grow 59 | // after the first growth iteration. 60 | wt[i] = 0 61 | } 62 | 63 | // Growth. 64 | for v := m; v < n; v++ { 65 | var u int 66 | pa: 67 | for i := 0; i < m; i++ { 68 | // Triad formation. 69 | if i != 0 && rnd() < p { 70 | for _, w := range permute(dst.From(simple.Node(u)), rndN) { 71 | wid := w.ID() 72 | if wid == v || dst.HasEdgeBetween(w, simple.Node(v)) { 73 | continue 74 | } 75 | dst.SetEdge(simple.Edge{F: w, T: simple.Node(v), W: 1}) 76 | wt[wid]++ 77 | wt[v]++ 78 | continue pa 79 | } 80 | } 81 | 82 | // Preferential attachment. 83 | for { 84 | var ok bool 85 | u, ok = ws.Take() 86 | if !ok { 87 | return errors.New("gen: depleted distribution") 88 | } 89 | if u == v || dst.HasEdgeBetween(simple.Node(u), simple.Node(v)) { 90 | continue 91 | } 92 | dst.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) 93 | wt[u]++ 94 | wt[v]++ 95 | break 96 | } 97 | } 98 | 99 | ws.ReweightAll(wt) 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func permute(n []graph.Node, rnd func(int) int) []graph.Node { 106 | for i := range n[:len(n)-1] { 107 | j := rnd(len(n)-i) + i 108 | n[i], n[j] = n[j], n[i] 109 | } 110 | return n 111 | } 112 | 113 | // PreferentialAttachment constructs a graph in the destination, dst, of order n. 114 | // The graph is constructed successively starting from an m order graph with one 115 | // node having degree m-1. At each iteration of graph addition, one node is added 116 | // with m additional edges joining existing nodes with probability proportional 117 | // to the nodes' degrees. If src is not nil it is used as the random source, 118 | // otherwise rand.Float64 is used. 119 | // 120 | // The algorithm is essentially as described in http://arxiv.org/abs/cond-mat/0110452 121 | // after 10.1126/science.286.5439.509. 122 | func PreferentialAttachment(dst graph.UndirectedBuilder, n, m int, src *rand.Rand) error { 123 | if n <= m { 124 | return fmt.Errorf("gen: n <= m: n=%v m=%d", n, m) 125 | } 126 | 127 | // Initial condition. 128 | wt := make([]float64, n) 129 | for u := 0; u < m; u++ { 130 | if !dst.Has(simple.Node(u)) { 131 | dst.AddNode(simple.Node(u)) 132 | } 133 | // We need to give equal probability for 134 | // adding the first generation of edges. 135 | wt[u] = 1 136 | } 137 | ws := sampleuv.NewWeighted(wt, src) 138 | for i := range wt { 139 | // These weights will organically grow 140 | // after the first growth iteration. 141 | wt[i] = 0 142 | } 143 | 144 | // Growth. 145 | for v := m; v < n; v++ { 146 | for i := 0; i < m; i++ { 147 | // Preferential attachment. 148 | u, ok := ws.Take() 149 | if !ok { 150 | return errors.New("gen: depleted distribution") 151 | } 152 | dst.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) 153 | wt[u]++ 154 | wt[v]++ 155 | } 156 | ws.ReweightAll(wt) 157 | } 158 | 159 | return nil 160 | } 161 | -------------------------------------------------------------------------------- /graphs/gen/holme_kim_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph/simple" 12 | ) 13 | 14 | func TestTunableClusteringScaleFree(t *testing.T) { 15 | for n := 2; n <= 20; n++ { 16 | for m := 0; m < n; m++ { 17 | for p := 0.; p <= 1; p += 0.1 { 18 | g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} 19 | err := TunableClusteringScaleFree(g, n, m, p, nil) 20 | if err != nil { 21 | t.Fatalf("unexpected error: n=%d, m=%d, p=%v: %v", n, m, p, err) 22 | } 23 | if g.addBackwards { 24 | t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d, p=%v", n, m, p) 25 | } 26 | if g.addSelfLoop { 27 | t.Errorf("unexpected self edge: n=%d, m=%d, p=%v", n, m, p) 28 | } 29 | if g.addMultipleEdge { 30 | t.Errorf("unexpected multiple edge: n=%d, m=%d, p=%v", n, m, p) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | func TestPreferentialAttachment(t *testing.T) { 38 | for n := 2; n <= 20; n++ { 39 | for m := 0; m < n; m++ { 40 | g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} 41 | err := PreferentialAttachment(g, n, m, nil) 42 | if err != nil { 43 | t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) 44 | } 45 | if g.addBackwards { 46 | t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d", n, m) 47 | } 48 | if g.addSelfLoop { 49 | t.Errorf("unexpected self edge: n=%d, m=%d", n, m) 50 | } 51 | if g.addMultipleEdge { 52 | t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /graphs/gen/small_world.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "math" 11 | "math/rand" 12 | 13 | "github.com/gonum/graph" 14 | "github.com/gonum/graph/simple" 15 | "github.com/gonum/stat/sampleuv" 16 | ) 17 | 18 | // NavigableSmallWorld constructs an N-dimensional grid with guaranteed local connectivity 19 | // and random long-range connectivity in the destination, dst. The dims parameters specifies 20 | // the length of each of the N dimensions, p defines the Manhattan distance between local 21 | // nodes, and q defines the number of out-going long-range connections from each node. Long- 22 | // range connections are made with a probability proportional to |d(u,v)|^-r where d is the 23 | // Manhattan distance between non-local nodes. 24 | // 25 | // The algorithm is essentially as described on p4 of http://www.cs.cornell.edu/home/kleinber/swn.pdf. 26 | func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src *rand.Rand) (err error) { 27 | if p < 1 { 28 | return fmt.Errorf("gen: bad local distance: p=%v", p) 29 | } 30 | if q < 0 { 31 | return fmt.Errorf("gen: bad distant link count: q=%v", q) 32 | } 33 | if r < 0 { 34 | return fmt.Errorf("gen: bad decay constant: r=%v", r) 35 | } 36 | 37 | n := 1 38 | for _, d := range dims { 39 | n *= d 40 | } 41 | for i := 0; i < n; i++ { 42 | if !dst.Has(simple.Node(i)) { 43 | dst.AddNode(simple.Node(i)) 44 | } 45 | } 46 | 47 | hasEdge := dst.HasEdgeBetween 48 | d, isDirected := dst.(graph.Directed) 49 | if isDirected { 50 | hasEdge = d.HasEdgeFromTo 51 | } 52 | 53 | locality := make([]int, len(dims)) 54 | for i := range locality { 55 | locality[i] = p*2 + 1 56 | } 57 | iterateOver(dims, func(u []int) { 58 | uid := idFrom(u, dims) 59 | iterateOver(locality, func(delta []int) { 60 | d := manhattanDelta(u, delta, dims, -p) 61 | if d == 0 || d > p { 62 | return 63 | } 64 | vid := idFromDelta(u, delta, dims, -p) 65 | e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid), W: 1} 66 | if uid > vid { 67 | e.F, e.T = e.T, e.F 68 | } 69 | if !hasEdge(e.From(), e.To()) { 70 | dst.SetEdge(e) 71 | } 72 | if !isDirected { 73 | return 74 | } 75 | e.F, e.T = e.T, e.F 76 | if !hasEdge(e.From(), e.To()) { 77 | dst.SetEdge(e) 78 | } 79 | }) 80 | }) 81 | 82 | defer func() { 83 | r := recover() 84 | if r != nil { 85 | if r != "depleted distribution" { 86 | panic(r) 87 | } 88 | err = errors.New("depleted distribution") 89 | } 90 | }() 91 | w := make([]float64, n) 92 | ws := sampleuv.NewWeighted(w, src) 93 | iterateOver(dims, func(u []int) { 94 | uid := idFrom(u, dims) 95 | iterateOver(dims, func(v []int) { 96 | d := manhattanBetween(u, v) 97 | if d <= p { 98 | return 99 | } 100 | w[idFrom(v, dims)] = math.Pow(float64(d), -r) 101 | }) 102 | ws.ReweightAll(w) 103 | for i := 0; i < q; i++ { 104 | vid, ok := ws.Take() 105 | if !ok { 106 | panic("depleted distribution") 107 | } 108 | e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid), W: 1} 109 | if !isDirected && uid > vid { 110 | e.F, e.T = e.T, e.F 111 | } 112 | if !hasEdge(e.From(), e.To()) { 113 | dst.SetEdge(e) 114 | } 115 | } 116 | for i := range w { 117 | w[i] = 0 118 | } 119 | }) 120 | 121 | return nil 122 | } 123 | 124 | // iterateOver performs an iteration over all dimensions of dims, calling fn 125 | // for each state. The elements of state must not be mutated by fn. 126 | func iterateOver(dims []int, fn func(state []int)) { 127 | iterator(0, dims, make([]int, len(dims)), fn) 128 | } 129 | 130 | func iterator(d int, dims, state []int, fn func(state []int)) { 131 | if d >= len(dims) { 132 | fn(state) 133 | return 134 | } 135 | for i := 0; i < dims[d]; i++ { 136 | state[d] = i 137 | iterator(d+1, dims, state, fn) 138 | } 139 | } 140 | 141 | // manhattanBetween returns the Manhattan distance between a and b. 142 | func manhattanBetween(a, b []int) int { 143 | if len(a) != len(b) { 144 | panic("gen: unexpected dimension") 145 | } 146 | var d int 147 | for i, v := range a { 148 | d += abs(v - b[i]) 149 | } 150 | return d 151 | } 152 | 153 | // manhattanDelta returns the Manhattan norm of delta+translate. If a 154 | // translated by delta+translate is out of the range given by dims, 155 | // zero is returned. 156 | func manhattanDelta(a, delta, dims []int, translate int) int { 157 | if len(a) != len(dims) { 158 | panic("gen: unexpected dimension") 159 | } 160 | if len(delta) != len(dims) { 161 | panic("gen: unexpected dimension") 162 | } 163 | var d int 164 | for i, v := range delta { 165 | v += translate 166 | t := a[i] + v 167 | if t < 0 || t >= dims[i] { 168 | return 0 169 | } 170 | d += abs(v) 171 | } 172 | return d 173 | } 174 | 175 | // idFrom returns a node id for the slice n over the given dimensions. 176 | func idFrom(n, dims []int) int { 177 | s := 1 178 | var id int 179 | for d, m := range dims { 180 | p := n[d] 181 | if p < 0 || p >= m { 182 | panic("gen: element out of range") 183 | } 184 | id += p * s 185 | s *= m 186 | } 187 | return id 188 | } 189 | 190 | // idFromDelta returns a node id for the slice base plus the delta over the given 191 | // dimensions and applying the translation. 192 | func idFromDelta(base, delta, dims []int, translate int) int { 193 | s := 1 194 | var id int 195 | for d, m := range dims { 196 | n := base[d] + delta[d] + translate 197 | if n < 0 || n >= m { 198 | panic("gen: element out of range") 199 | } 200 | id += n * s 201 | s *= m 202 | } 203 | return id 204 | } 205 | -------------------------------------------------------------------------------- /graphs/gen/small_world_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph/simple" 12 | ) 13 | 14 | var smallWorldDimensionParameters = [][]int{ 15 | {50}, 16 | {10, 10}, 17 | {6, 5, 4}, 18 | } 19 | 20 | func TestNavigableSmallWorldUndirected(t *testing.T) { 21 | for p := 1; p < 5; p++ { 22 | for q := 0; q < 10; q++ { 23 | for r := 0.5; r < 10; r++ { 24 | for _, dims := range smallWorldDimensionParameters { 25 | g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} 26 | err := NavigableSmallWorld(g, dims, p, q, r, nil) 27 | n := 1 28 | for _, d := range dims { 29 | n *= d 30 | } 31 | if err != nil { 32 | t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v: %v", dims, n, p, q, r, err) 33 | } 34 | if g.addBackwards { 35 | t.Errorf("edge added with From.ID > To.ID: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) 36 | } 37 | if g.addSelfLoop { 38 | t.Errorf("unexpected self edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) 39 | } 40 | if g.addMultipleEdge { 41 | t.Errorf("unexpected multiple edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | func TestNavigableSmallWorldDirected(t *testing.T) { 50 | for p := 1; p < 5; p++ { 51 | for q := 0; q < 10; q++ { 52 | for r := 0.5; r < 10; r++ { 53 | for _, dims := range smallWorldDimensionParameters { 54 | g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} 55 | err := NavigableSmallWorld(g, dims, p, q, r, nil) 56 | n := 1 57 | for _, d := range dims { 58 | n *= d 59 | } 60 | if err != nil { 61 | t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v, r=%v: %v", dims, n, p, q, r, err) 62 | } 63 | if g.addSelfLoop { 64 | t.Errorf("unexpected self edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) 65 | } 66 | if g.addMultipleEdge { 67 | t.Errorf("unexpected multiple edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /internal/linear/linear.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package linear provides common linear data structures. 9 | package linear 10 | 11 | import ( 12 | "github.com/gonum/graph" 13 | ) 14 | 15 | // NodeStack implements a LIFO stack of graph.Node. 16 | type NodeStack []graph.Node 17 | 18 | // Len returns the number of graph.Nodes on the stack. 19 | func (s *NodeStack) Len() int { return len(*s) } 20 | 21 | // Pop returns the last graph.Node on the stack and removes it 22 | // from the stack. 23 | func (s *NodeStack) Pop() graph.Node { 24 | v := *s 25 | v, n := v[:len(v)-1], v[len(v)-1] 26 | *s = v 27 | return n 28 | } 29 | 30 | // Push adds the node n to the stack at the last position. 31 | func (s *NodeStack) Push(n graph.Node) { *s = append(*s, n) } 32 | 33 | // NodeQueue implements a FIFO queue. 34 | type NodeQueue struct { 35 | head int 36 | data []graph.Node 37 | } 38 | 39 | // Len returns the number of graph.Nodes in the queue. 40 | func (q *NodeQueue) Len() int { return len(q.data) - q.head } 41 | 42 | // Enqueue adds the node n to the back of the queue. 43 | func (q *NodeQueue) Enqueue(n graph.Node) { 44 | if len(q.data) == cap(q.data) && q.head > 0 { 45 | l := q.Len() 46 | copy(q.data, q.data[q.head:]) 47 | q.head = 0 48 | q.data = append(q.data[:l], n) 49 | } else { 50 | q.data = append(q.data, n) 51 | } 52 | } 53 | 54 | // Dequeue returns the graph.Node at the front of the queue and 55 | // removes it from the queue. 56 | func (q *NodeQueue) Dequeue() graph.Node { 57 | if q.Len() == 0 { 58 | panic("queue: empty queue") 59 | } 60 | 61 | var n graph.Node 62 | n, q.data[q.head] = q.data[q.head], nil 63 | q.head++ 64 | 65 | if q.Len() == 0 { 66 | q.head = 0 67 | q.data = q.data[:0] 68 | } 69 | 70 | return n 71 | } 72 | 73 | // Reset clears the queue for reuse. 74 | func (q *NodeQueue) Reset() { 75 | q.head = 0 76 | q.data = q.data[:0] 77 | } 78 | -------------------------------------------------------------------------------- /internal/ordered/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package ordered provides common sort ordering types. 9 | package ordered 10 | 11 | import "github.com/gonum/graph" 12 | 13 | // ByID implements the sort.Interface sorting a slice of graph.Node 14 | // by ID. 15 | type ByID []graph.Node 16 | 17 | func (n ByID) Len() int { return len(n) } 18 | func (n ByID) Less(i, j int) bool { return n[i].ID() < n[j].ID() } 19 | func (n ByID) Swap(i, j int) { n[i], n[j] = n[j], n[i] } 20 | 21 | // BySliceValues implements the sort.Interface sorting a slice of 22 | // []int lexically by the values of the []int. 23 | type BySliceValues [][]int 24 | 25 | func (c BySliceValues) Len() int { return len(c) } 26 | func (c BySliceValues) Less(i, j int) bool { 27 | a, b := c[i], c[j] 28 | l := len(a) 29 | if len(b) < l { 30 | l = len(b) 31 | } 32 | for k, v := range a[:l] { 33 | if v < b[k] { 34 | return true 35 | } 36 | if v > b[k] { 37 | return false 38 | } 39 | } 40 | return len(a) < len(b) 41 | } 42 | func (c BySliceValues) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 43 | 44 | // BySliceIDs implements the sort.Interface sorting a slice of 45 | // []graph.Node lexically by the IDs of the []graph.Node. 46 | type BySliceIDs [][]graph.Node 47 | 48 | func (c BySliceIDs) Len() int { return len(c) } 49 | func (c BySliceIDs) Less(i, j int) bool { 50 | a, b := c[i], c[j] 51 | l := len(a) 52 | if len(b) < l { 53 | l = len(b) 54 | } 55 | for k, v := range a[:l] { 56 | if v.ID() < b[k].ID() { 57 | return true 58 | } 59 | if v.ID() > b[k].ID() { 60 | return false 61 | } 62 | } 63 | return len(a) < len(b) 64 | } 65 | func (c BySliceIDs) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 66 | -------------------------------------------------------------------------------- /internal/set/same.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //+build !appengine 6 | 7 | package set 8 | 9 | import "unsafe" 10 | 11 | // same determines whether two sets are backed by the same store. In the 12 | // current implementation using hash maps it makes use of the fact that 13 | // hash maps are passed as a pointer to a runtime Hmap struct. A map is 14 | // not seen by the runtime as a pointer though, so we use unsafe to get 15 | // the maps' pointer values to compare. 16 | func same(a, b Nodes) bool { 17 | return *(*uintptr)(unsafe.Pointer(&a)) == *(*uintptr)(unsafe.Pointer(&b)) 18 | } 19 | -------------------------------------------------------------------------------- /internal/set/same_appengine.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //+build appengine 6 | 7 | package set 8 | 9 | import "reflect" 10 | 11 | // same determines whether two sets are backed by the same store. In the 12 | // current implementation using hash maps it makes use of the fact that 13 | // hash maps are passed as a pointer to a runtime Hmap struct. A map is 14 | // not seen by the runtime as a pointer though, so we use reflect to get 15 | // the maps' pointer values to compare. 16 | func same(a, b Nodes) bool { 17 | return reflect.ValueOf(a).Pointer() == reflect.ValueOf(b).Pointer() 18 | } 19 | -------------------------------------------------------------------------------- /internal/set/set.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package set provides integer and graph.Node sets. 9 | package set 10 | 11 | import "github.com/gonum/graph" 12 | 13 | // Ints is a set of integer identifiers. 14 | type Ints map[int]struct{} 15 | 16 | // The simple accessor methods for Ints are provided to allow ease of 17 | // implementation change should the need arise. 18 | 19 | // Add inserts an element into the set. 20 | func (s Ints) Add(e int) { 21 | s[e] = struct{}{} 22 | } 23 | 24 | // Has reports the existence of the element in the set. 25 | func (s Ints) Has(e int) bool { 26 | _, ok := s[e] 27 | return ok 28 | } 29 | 30 | // Remove deletes the specified element from the set. 31 | func (s Ints) Remove(e int) { 32 | delete(s, e) 33 | } 34 | 35 | // Count reports the number of elements stored in the set. 36 | func (s Ints) Count() int { 37 | return len(s) 38 | } 39 | 40 | // Nodes is a set of nodes keyed in their integer identifiers. 41 | type Nodes map[int]graph.Node 42 | 43 | // The simple accessor methods for Nodes are provided to allow ease of 44 | // implementation change should the need arise. 45 | 46 | // Add inserts an element into the set. 47 | func (s Nodes) Add(n graph.Node) { 48 | s[n.ID()] = n 49 | } 50 | 51 | // Remove deletes the specified element from the set. 52 | func (s Nodes) Remove(e graph.Node) { 53 | delete(s, e.ID()) 54 | } 55 | 56 | // Has reports the existence of the element in the set. 57 | func (s Nodes) Has(n graph.Node) bool { 58 | _, ok := s[n.ID()] 59 | return ok 60 | } 61 | 62 | // clear clears the set, possibly using the same backing store. 63 | func (s *Nodes) clear() { 64 | if len(*s) != 0 { 65 | *s = make(Nodes) 66 | } 67 | } 68 | 69 | // Copy performs a perfect copy from src to dst (meaning the sets will 70 | // be equal). 71 | func (dst Nodes) Copy(src Nodes) Nodes { 72 | if same(src, dst) { 73 | return dst 74 | } 75 | 76 | if len(dst) > 0 { 77 | dst = make(Nodes, len(src)) 78 | } 79 | 80 | for e, n := range src { 81 | dst[e] = n 82 | } 83 | 84 | return dst 85 | } 86 | 87 | // Equal reports set equality between the parameters. Sets are equal if 88 | // and only if they have the same elements. 89 | func Equal(a, b Nodes) bool { 90 | if same(a, b) { 91 | return true 92 | } 93 | 94 | if len(a) != len(b) { 95 | return false 96 | } 97 | 98 | for e := range a { 99 | if _, ok := b[e]; !ok { 100 | return false 101 | } 102 | } 103 | 104 | return true 105 | } 106 | 107 | // Union takes the union of a and b, and stores it in dst. 108 | // 109 | // The union of two sets, a and b, is the set containing all the 110 | // elements of each, for instance: 111 | // 112 | // {a,b,c} UNION {d,e,f} = {a,b,c,d,e,f} 113 | // 114 | // Since sets may not have repetition, unions of two sets that overlap 115 | // do not contain repeat elements, that is: 116 | // 117 | // {a,b,c} UNION {b,c,d} = {a,b,c,d} 118 | // 119 | func (dst Nodes) Union(a, b Nodes) Nodes { 120 | if same(a, b) { 121 | return dst.Copy(a) 122 | } 123 | 124 | if !same(a, dst) && !same(b, dst) { 125 | dst.clear() 126 | } 127 | 128 | if !same(dst, a) { 129 | for e, n := range a { 130 | dst[e] = n 131 | } 132 | } 133 | 134 | if !same(dst, b) { 135 | for e, n := range b { 136 | dst[e] = n 137 | } 138 | } 139 | 140 | return dst 141 | } 142 | 143 | // Intersect takes the intersection of a and b, and stores it in dst. 144 | // 145 | // The intersection of two sets, a and b, is the set containing all 146 | // the elements shared between the two sets, for instance: 147 | // 148 | // {a,b,c} INTERSECT {b,c,d} = {b,c} 149 | // 150 | // The intersection between a set and itself is itself, and thus 151 | // effectively a copy operation: 152 | // 153 | // {a,b,c} INTERSECT {a,b,c} = {a,b,c} 154 | // 155 | // The intersection between two sets that share no elements is the empty 156 | // set: 157 | // 158 | // {a,b,c} INTERSECT {d,e,f} = {} 159 | // 160 | func (dst Nodes) Intersect(a, b Nodes) Nodes { 161 | var swap Nodes 162 | 163 | if same(a, b) { 164 | return dst.Copy(a) 165 | } 166 | if same(a, dst) { 167 | swap = b 168 | } else if same(b, dst) { 169 | swap = a 170 | } else { 171 | dst.clear() 172 | 173 | if len(a) > len(b) { 174 | a, b = b, a 175 | } 176 | 177 | for e, n := range a { 178 | if _, ok := b[e]; ok { 179 | dst[e] = n 180 | } 181 | } 182 | 183 | return dst 184 | } 185 | 186 | for e := range dst { 187 | if _, ok := swap[e]; !ok { 188 | delete(dst, e) 189 | } 190 | } 191 | 192 | return dst 193 | } 194 | -------------------------------------------------------------------------------- /network/distance.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package network 6 | 7 | import ( 8 | "math" 9 | 10 | "github.com/gonum/graph" 11 | "github.com/gonum/graph/path" 12 | ) 13 | 14 | // Closeness returns the closeness centrality for nodes in the graph g used to 15 | // construct the given shortest paths. 16 | // 17 | // C(v) = 1 / \sum_u d(u,v) 18 | // 19 | // For directed graphs the incoming paths are used. Infinite distances are 20 | // not considered. 21 | func Closeness(g graph.Graph, p path.AllShortest) map[int]float64 { 22 | nodes := g.Nodes() 23 | c := make(map[int]float64, len(nodes)) 24 | for _, u := range nodes { 25 | var sum float64 26 | for _, v := range nodes { 27 | // The ordering here is not relevant for 28 | // undirected graphs, but we make sure we 29 | // are counting incoming paths. 30 | d := p.Weight(v, u) 31 | if math.IsInf(d, 0) { 32 | continue 33 | } 34 | sum += d 35 | } 36 | c[u.ID()] = 1 / sum 37 | } 38 | return c 39 | } 40 | 41 | // Farness returns the farness for nodes in the graph g used to construct 42 | // the given shortest paths. 43 | // 44 | // F(v) = \sum_u d(u,v) 45 | // 46 | // For directed graphs the incoming paths are used. Infinite distances are 47 | // not considered. 48 | func Farness(g graph.Graph, p path.AllShortest) map[int]float64 { 49 | nodes := g.Nodes() 50 | f := make(map[int]float64, len(nodes)) 51 | for _, u := range nodes { 52 | var sum float64 53 | for _, v := range nodes { 54 | // The ordering here is not relevant for 55 | // undirected graphs, but we make sure we 56 | // are counting incoming paths. 57 | d := p.Weight(v, u) 58 | if math.IsInf(d, 0) { 59 | continue 60 | } 61 | sum += d 62 | } 63 | f[u.ID()] = sum 64 | } 65 | return f 66 | } 67 | 68 | // Harmonic returns the harmonic centrality for nodes in the graph g used to 69 | // construct the given shortest paths. 70 | // 71 | // H(v)= \sum_{u ≠ v} 1 / d(u,v) 72 | // 73 | // For directed graphs the incoming paths are used. Infinite distances are 74 | // not considered. 75 | func Harmonic(g graph.Graph, p path.AllShortest) map[int]float64 { 76 | nodes := g.Nodes() 77 | h := make(map[int]float64, len(nodes)) 78 | for i, u := range nodes { 79 | var sum float64 80 | for j, v := range nodes { 81 | // The ordering here is not relevant for 82 | // undirected graphs, but we make sure we 83 | // are counting incoming paths. 84 | d := p.Weight(v, u) 85 | if math.IsInf(d, 0) { 86 | continue 87 | } 88 | if i != j { 89 | sum += 1 / d 90 | } 91 | } 92 | h[u.ID()] = sum 93 | } 94 | return h 95 | } 96 | 97 | // Residual returns the Dangalchev's residual closeness for nodes in the graph 98 | // g used to construct the given shortest paths. 99 | // 100 | // C(v)= \sum_{u ≠ v} 1 / 2^d(u,v) 101 | // 102 | // For directed graphs the incoming paths are used. Infinite distances are 103 | // not considered. 104 | func Residual(g graph.Graph, p path.AllShortest) map[int]float64 { 105 | nodes := g.Nodes() 106 | r := make(map[int]float64, len(nodes)) 107 | for i, u := range nodes { 108 | var sum float64 109 | for j, v := range nodes { 110 | // The ordering here is not relevant for 111 | // undirected graphs, but we make sure we 112 | // are counting incoming paths. 113 | d := p.Weight(v, u) 114 | if math.IsInf(d, 0) { 115 | continue 116 | } 117 | if i != j { 118 | sum += math.Exp2(-d) 119 | } 120 | } 121 | r[u.ID()] = sum 122 | } 123 | return r 124 | } 125 | -------------------------------------------------------------------------------- /network/hits.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package network 6 | 7 | import ( 8 | "math" 9 | 10 | "github.com/gonum/floats" 11 | "github.com/gonum/graph" 12 | ) 13 | 14 | // HubAuthority is a Hyperlink-Induced Topic Search hub-authority score pair. 15 | type HubAuthority struct { 16 | Hub float64 17 | Authority float64 18 | } 19 | 20 | // HITS returns the Hyperlink-Induced Topic Search hub-authority scores for 21 | // nodes of the directed graph g. HITS terminates when the 2-norm of the 22 | // vector difference between iterations is below tol. The returned map is 23 | // keyed on the graph node IDs. 24 | func HITS(g graph.Directed, tol float64) map[int]HubAuthority { 25 | nodes := g.Nodes() 26 | 27 | // Make a topological copy of g with dense node IDs. 28 | indexOf := make(map[int]int, len(nodes)) 29 | for i, n := range nodes { 30 | indexOf[n.ID()] = i 31 | } 32 | nodesLinkingTo := make([][]int, len(nodes)) 33 | nodesLinkedFrom := make([][]int, len(nodes)) 34 | for i, n := range nodes { 35 | for _, u := range g.To(n) { 36 | nodesLinkingTo[i] = append(nodesLinkingTo[i], indexOf[u.ID()]) 37 | } 38 | for _, v := range g.From(n) { 39 | nodesLinkedFrom[i] = append(nodesLinkedFrom[i], indexOf[v.ID()]) 40 | } 41 | } 42 | indexOf = nil 43 | 44 | w := make([]float64, 4*len(nodes)) 45 | auth := w[:len(nodes)] 46 | hub := w[len(nodes) : 2*len(nodes)] 47 | for i := range nodes { 48 | auth[i] = 1 49 | hub[i] = 1 50 | } 51 | deltaAuth := w[2*len(nodes) : 3*len(nodes)] 52 | deltaHub := w[3*len(nodes):] 53 | 54 | var norm float64 55 | for { 56 | norm = 0 57 | for v := range nodes { 58 | var a float64 59 | for _, u := range nodesLinkingTo[v] { 60 | a += hub[u] 61 | } 62 | deltaAuth[v] = auth[v] 63 | auth[v] = a 64 | norm += a * a 65 | } 66 | norm = math.Sqrt(norm) 67 | 68 | for i := range auth { 69 | auth[i] /= norm 70 | deltaAuth[i] -= auth[i] 71 | } 72 | 73 | norm = 0 74 | for u := range nodes { 75 | var h float64 76 | for _, v := range nodesLinkedFrom[u] { 77 | h += auth[v] 78 | } 79 | deltaHub[u] = hub[u] 80 | hub[u] = h 81 | norm += h * h 82 | } 83 | norm = math.Sqrt(norm) 84 | 85 | for i := range hub { 86 | hub[i] /= norm 87 | deltaHub[i] -= hub[i] 88 | } 89 | 90 | if floats.Norm(deltaAuth, 2) < tol && floats.Norm(deltaHub, 2) < tol { 91 | break 92 | } 93 | } 94 | 95 | hubAuth := make(map[int]HubAuthority, len(nodes)) 96 | for i, n := range nodes { 97 | hubAuth[n.ID()] = HubAuthority{Hub: hub[i], Authority: auth[i]} 98 | } 99 | 100 | return hubAuth 101 | } 102 | -------------------------------------------------------------------------------- /network/hits_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package network 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/floats" 14 | "github.com/gonum/graph/simple" 15 | ) 16 | 17 | var hitsTests = []struct { 18 | g []set 19 | tol float64 20 | 21 | wantTol float64 22 | want map[int]HubAuthority 23 | }{ 24 | { 25 | // Example graph from http://www.cis.hut.fi/Opinnot/T-61.6020/2008/pagerank_hits.pdf page 8. 26 | g: []set{ 27 | A: linksTo(B, C, D), 28 | B: linksTo(C, D), 29 | C: linksTo(B), 30 | D: nil, 31 | }, 32 | tol: 1e-4, 33 | 34 | wantTol: 1e-4, 35 | want: map[int]HubAuthority{ 36 | A: {Hub: 0.7887, Authority: 0}, 37 | B: {Hub: 0.5774, Authority: 0.4597}, 38 | C: {Hub: 0.2113, Authority: 0.6280}, 39 | D: {Hub: 0, Authority: 0.6280}, 40 | }, 41 | }, 42 | } 43 | 44 | func TestHITS(t *testing.T) { 45 | for i, test := range hitsTests { 46 | g := simple.NewDirectedGraph(0, math.Inf(1)) 47 | for u, e := range test.g { 48 | // Add nodes that are not defined by an edge. 49 | if !g.Has(simple.Node(u)) { 50 | g.AddNode(simple.Node(u)) 51 | } 52 | for v := range e { 53 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 54 | } 55 | } 56 | got := HITS(g, test.tol) 57 | prec := 1 - int(math.Log10(test.wantTol)) 58 | for n := range test.g { 59 | if !floats.EqualWithinAbsOrRel(got[n].Hub, test.want[n].Hub, test.wantTol, test.wantTol) { 60 | t.Errorf("unexpected HITS result for test %d:\ngot: %v\nwant:%v", 61 | i, orderedHubAuth(got, prec), orderedHubAuth(test.want, prec)) 62 | break 63 | } 64 | if !floats.EqualWithinAbsOrRel(got[n].Authority, test.want[n].Authority, test.wantTol, test.wantTol) { 65 | t.Errorf("unexpected HITS result for test %d:\ngot: %v\nwant:%v", 66 | i, orderedHubAuth(got, prec), orderedHubAuth(test.want, prec)) 67 | break 68 | } 69 | } 70 | } 71 | } 72 | 73 | func orderedHubAuth(w map[int]HubAuthority, prec int) []keyHubAuthVal { 74 | o := make(orderedHubAuthMap, 0, len(w)) 75 | for k, v := range w { 76 | o = append(o, keyHubAuthVal{prec: prec, key: k, val: v}) 77 | } 78 | sort.Sort(o) 79 | return o 80 | } 81 | 82 | type keyHubAuthVal struct { 83 | prec int 84 | key int 85 | val HubAuthority 86 | } 87 | 88 | func (kv keyHubAuthVal) String() string { 89 | return fmt.Sprintf("%d:{H:%.*f, A:%.*f}", 90 | kv.key, kv.prec, kv.val.Hub, kv.prec, kv.val.Authority, 91 | ) 92 | } 93 | 94 | type orderedHubAuthMap []keyHubAuthVal 95 | 96 | func (o orderedHubAuthMap) Len() int { return len(o) } 97 | func (o orderedHubAuthMap) Less(i, j int) bool { return o[i].key < o[j].key } 98 | func (o orderedHubAuthMap) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 99 | -------------------------------------------------------------------------------- /network/network.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // TODO(kortschak): Implement: 6 | // * edge-weighted PageRank and HITS 7 | // * PersonalizedPageRank: 8 | // http://infolab.stanford.edu/~backrub/google.html 2.1.2 Intuitive Justification 9 | // http://ilpubs.stanford.edu:8090/596/1/2003-35.pdf 10 | // http://www.vldb.org/pvldb/vol7/p1023-maehara.pdf 11 | // * other centrality measures 12 | 13 | // This repository is no longer maintained. 14 | // Development has moved to https://github.com/gonum/gonum. 15 | // 16 | // Package network provides network analysis functions. 17 | package network 18 | -------------------------------------------------------------------------------- /network/network_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package network 6 | 7 | const ( 8 | A = iota 9 | B 10 | C 11 | D 12 | E 13 | F 14 | G 15 | H 16 | I 17 | J 18 | K 19 | ) 20 | 21 | // set is an integer set. 22 | type set map[int]struct{} 23 | 24 | func linksTo(i ...int) set { 25 | if len(i) == 0 { 26 | return nil 27 | } 28 | s := make(set) 29 | for _, v := range i { 30 | s[v] = struct{}{} 31 | } 32 | return s 33 | } 34 | -------------------------------------------------------------------------------- /network/page_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package network 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/floats" 14 | "github.com/gonum/graph/simple" 15 | ) 16 | 17 | var pageRankTests = []struct { 18 | g []set 19 | damp float64 20 | tol float64 21 | 22 | wantTol float64 23 | want map[int]float64 24 | }{ 25 | { 26 | // Example graph from http://en.wikipedia.org/wiki/File:PageRanks-Example.svg 16:17, 8 July 2009 27 | g: []set{ 28 | A: nil, 29 | B: linksTo(C), 30 | C: linksTo(B), 31 | D: linksTo(A, B), 32 | E: linksTo(D, B, F), 33 | F: linksTo(B, E), 34 | G: linksTo(B, E), 35 | H: linksTo(B, E), 36 | I: linksTo(B, E), 37 | J: linksTo(E), 38 | K: linksTo(E), 39 | }, 40 | damp: 0.85, 41 | tol: 1e-8, 42 | 43 | wantTol: 1e-8, 44 | want: map[int]float64{ 45 | A: 0.03278149, 46 | B: 0.38440095, 47 | C: 0.34291029, 48 | D: 0.03908709, 49 | E: 0.08088569, 50 | F: 0.03908709, 51 | G: 0.01616948, 52 | H: 0.01616948, 53 | I: 0.01616948, 54 | J: 0.01616948, 55 | K: 0.01616948, 56 | }, 57 | }, 58 | { 59 | // Example graph from http://en.wikipedia.org/w/index.php?title=PageRank&oldid=659286279#Power_Method 60 | // Expected result calculated with the given MATLAB code. 61 | g: []set{ 62 | A: linksTo(B, C), 63 | B: linksTo(D), 64 | C: linksTo(D, E), 65 | D: linksTo(E), 66 | E: linksTo(A), 67 | }, 68 | damp: 0.80, 69 | tol: 1e-3, 70 | 71 | wantTol: 1e-3, 72 | want: map[int]float64{ 73 | A: 0.250, 74 | B: 0.140, 75 | C: 0.140, 76 | D: 0.208, 77 | E: 0.262, 78 | }, 79 | }, 80 | } 81 | 82 | func TestPageRank(t *testing.T) { 83 | for i, test := range pageRankTests { 84 | g := simple.NewDirectedGraph(0, math.Inf(1)) 85 | for u, e := range test.g { 86 | // Add nodes that are not defined by an edge. 87 | if !g.Has(simple.Node(u)) { 88 | g.AddNode(simple.Node(u)) 89 | } 90 | for v := range e { 91 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 92 | } 93 | } 94 | got := PageRank(g, test.damp, test.tol) 95 | prec := 1 - int(math.Log10(test.wantTol)) 96 | for n := range test.g { 97 | if !floats.EqualWithinAbsOrRel(got[n], test.want[n], test.wantTol, test.wantTol) { 98 | t.Errorf("unexpected PageRank result for test %d:\ngot: %v\nwant:%v", 99 | i, orderedFloats(got, prec), orderedFloats(test.want, prec)) 100 | break 101 | } 102 | } 103 | } 104 | } 105 | 106 | func TestPageRankSparse(t *testing.T) { 107 | for i, test := range pageRankTests { 108 | g := simple.NewDirectedGraph(0, math.Inf(1)) 109 | for u, e := range test.g { 110 | // Add nodes that are not defined by an edge. 111 | if !g.Has(simple.Node(u)) { 112 | g.AddNode(simple.Node(u)) 113 | } 114 | for v := range e { 115 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 116 | } 117 | } 118 | got := PageRankSparse(g, test.damp, test.tol) 119 | prec := 1 - int(math.Log10(test.wantTol)) 120 | for n := range test.g { 121 | if !floats.EqualWithinAbsOrRel(got[n], test.want[n], test.wantTol, test.wantTol) { 122 | t.Errorf("unexpected PageRank result for test %d:\ngot: %v\nwant:%v", 123 | i, orderedFloats(got, prec), orderedFloats(test.want, prec)) 124 | break 125 | } 126 | } 127 | } 128 | } 129 | 130 | func orderedFloats(w map[int]float64, prec int) []keyFloatVal { 131 | o := make(orderedFloatsMap, 0, len(w)) 132 | for k, v := range w { 133 | o = append(o, keyFloatVal{prec: prec, key: k, val: v}) 134 | } 135 | sort.Sort(o) 136 | return o 137 | } 138 | 139 | type keyFloatVal struct { 140 | prec int 141 | key int 142 | val float64 143 | } 144 | 145 | func (kv keyFloatVal) String() string { return fmt.Sprintf("%c:%.*f", kv.key+'A', kv.prec, kv.val) } 146 | 147 | type orderedFloatsMap []keyFloatVal 148 | 149 | func (o orderedFloatsMap) Len() int { return len(o) } 150 | func (o orderedFloatsMap) Less(i, j int) bool { return o[i].key < o[j].key } 151 | func (o orderedFloatsMap) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 152 | -------------------------------------------------------------------------------- /path/a_star.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "container/heap" 9 | 10 | "github.com/gonum/graph" 11 | "github.com/gonum/graph/internal/set" 12 | ) 13 | 14 | // AStar finds the A*-shortest path from s to t in g using the heuristic h. The path and 15 | // its cost are returned in a Shortest along with paths and costs to all nodes explored 16 | // during the search. The number of expanded nodes is also returned. This value may help 17 | // with heuristic tuning. 18 | // 19 | // The path will be the shortest path if the heuristic is admissible. A heuristic is 20 | // admissible if for any node, n, in the graph, the heuristic estimate of the cost of 21 | // the path from n to t is less than or equal to the true cost of that path. 22 | // 23 | // If h is nil, AStar will use the g.HeuristicCost method if g implements HeuristicCoster, 24 | // falling back to NullHeuristic otherwise. If the graph does not implement graph.Weighter, 25 | // UniformCost is used. AStar will panic if g has an A*-reachable negative edge weight. 26 | func AStar(s, t graph.Node, g graph.Graph, h Heuristic) (path Shortest, expanded int) { 27 | if !g.Has(s) || !g.Has(t) { 28 | return Shortest{from: s}, 0 29 | } 30 | var weight Weighting 31 | if wg, ok := g.(graph.Weighter); ok { 32 | weight = wg.Weight 33 | } else { 34 | weight = UniformCost(g) 35 | } 36 | if h == nil { 37 | if g, ok := g.(HeuristicCoster); ok { 38 | h = g.HeuristicCost 39 | } else { 40 | h = NullHeuristic 41 | } 42 | } 43 | 44 | path = newShortestFrom(s, g.Nodes()) 45 | tid := t.ID() 46 | 47 | visited := make(set.Ints) 48 | open := &aStarQueue{indexOf: make(map[int]int)} 49 | heap.Push(open, aStarNode{node: s, gscore: 0, fscore: h(s, t)}) 50 | 51 | for open.Len() != 0 { 52 | u := heap.Pop(open).(aStarNode) 53 | uid := u.node.ID() 54 | i := path.indexOf[uid] 55 | expanded++ 56 | 57 | if uid == tid { 58 | break 59 | } 60 | 61 | visited.Add(uid) 62 | for _, v := range g.From(u.node) { 63 | vid := v.ID() 64 | if visited.Has(vid) { 65 | continue 66 | } 67 | j := path.indexOf[vid] 68 | 69 | w, ok := weight(u.node, v) 70 | if !ok { 71 | panic("A*: unexpected invalid weight") 72 | } 73 | if w < 0 { 74 | panic("A*: negative edge weight") 75 | } 76 | g := u.gscore + w 77 | if n, ok := open.node(vid); !ok { 78 | path.set(j, g, i) 79 | heap.Push(open, aStarNode{node: v, gscore: g, fscore: g + h(v, t)}) 80 | } else if g < n.gscore { 81 | path.set(j, g, i) 82 | open.update(vid, g, g+h(v, t)) 83 | } 84 | } 85 | } 86 | 87 | return path, expanded 88 | } 89 | 90 | // NullHeuristic is an admissible, consistent heuristic that will not speed up computation. 91 | func NullHeuristic(_, _ graph.Node) float64 { 92 | return 0 93 | } 94 | 95 | // aStarNode adds A* accounting to a graph.Node. 96 | type aStarNode struct { 97 | node graph.Node 98 | gscore float64 99 | fscore float64 100 | } 101 | 102 | // aStarQueue is an A* priority queue. 103 | type aStarQueue struct { 104 | indexOf map[int]int 105 | nodes []aStarNode 106 | } 107 | 108 | func (q *aStarQueue) Less(i, j int) bool { 109 | return q.nodes[i].fscore < q.nodes[j].fscore 110 | } 111 | 112 | func (q *aStarQueue) Swap(i, j int) { 113 | q.indexOf[q.nodes[i].node.ID()] = j 114 | q.indexOf[q.nodes[j].node.ID()] = i 115 | q.nodes[i], q.nodes[j] = q.nodes[j], q.nodes[i] 116 | } 117 | 118 | func (q *aStarQueue) Len() int { 119 | return len(q.nodes) 120 | } 121 | 122 | func (q *aStarQueue) Push(x interface{}) { 123 | n := x.(aStarNode) 124 | q.indexOf[n.node.ID()] = len(q.nodes) 125 | q.nodes = append(q.nodes, n) 126 | } 127 | 128 | func (q *aStarQueue) Pop() interface{} { 129 | n := q.nodes[len(q.nodes)-1] 130 | q.nodes = q.nodes[:len(q.nodes)-1] 131 | delete(q.indexOf, n.node.ID()) 132 | return n 133 | } 134 | 135 | func (q *aStarQueue) update(id int, g, f float64) { 136 | i, ok := q.indexOf[id] 137 | if !ok { 138 | return 139 | } 140 | q.nodes[i].gscore = g 141 | q.nodes[i].fscore = f 142 | heap.Fix(q, i) 143 | } 144 | 145 | func (q *aStarQueue) node(id int) (aStarNode, bool) { 146 | loc, ok := q.indexOf[id] 147 | if ok { 148 | return q.nodes[loc], true 149 | } 150 | return aStarNode{}, false 151 | } 152 | -------------------------------------------------------------------------------- /path/bellman_ford_moore.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import "github.com/gonum/graph" 8 | 9 | // BellmanFordFrom returns a shortest-path tree for a shortest path from u to all nodes in 10 | // the graph g, or false indicating that a negative cycle exists in the graph. If the graph 11 | // does not implement graph.Weighter, UniformCost is used. 12 | // 13 | // The time complexity of BellmanFordFrom is O(|V|.|E|). 14 | func BellmanFordFrom(u graph.Node, g graph.Graph) (path Shortest, ok bool) { 15 | if !g.Has(u) { 16 | return Shortest{from: u}, true 17 | } 18 | var weight Weighting 19 | if wg, ok := g.(graph.Weighter); ok { 20 | weight = wg.Weight 21 | } else { 22 | weight = UniformCost(g) 23 | } 24 | 25 | nodes := g.Nodes() 26 | 27 | path = newShortestFrom(u, nodes) 28 | path.dist[path.indexOf[u.ID()]] = 0 29 | 30 | // TODO(kortschak): Consider adding further optimisations 31 | // from http://arxiv.org/abs/1111.5414. 32 | for i := 1; i < len(nodes); i++ { 33 | changed := false 34 | for j, u := range nodes { 35 | for _, v := range g.From(u) { 36 | k := path.indexOf[v.ID()] 37 | w, ok := weight(u, v) 38 | if !ok { 39 | panic("bellman-ford: unexpected invalid weight") 40 | } 41 | joint := path.dist[j] + w 42 | if joint < path.dist[k] { 43 | path.set(k, joint, j) 44 | changed = true 45 | } 46 | } 47 | } 48 | if !changed { 49 | break 50 | } 51 | } 52 | 53 | for j, u := range nodes { 54 | for _, v := range g.From(u) { 55 | k := path.indexOf[v.ID()] 56 | w, ok := weight(u, v) 57 | if !ok { 58 | panic("bellman-ford: unexpected invalid weight") 59 | } 60 | if path.dist[j]+w < path.dist[k] { 61 | return path, false 62 | } 63 | } 64 | } 65 | 66 | return path, true 67 | } 68 | -------------------------------------------------------------------------------- /path/bellman_ford_moore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/gonum/graph" 13 | "github.com/gonum/graph/path/internal/testgraphs" 14 | ) 15 | 16 | func TestBellmanFordFrom(t *testing.T) { 17 | for _, test := range testgraphs.ShortestPathTests { 18 | g := test.Graph() 19 | for _, e := range test.Edges { 20 | g.SetEdge(e) 21 | } 22 | 23 | pt, ok := BellmanFordFrom(test.Query.From(), g.(graph.Graph)) 24 | if test.HasNegativeCycle { 25 | if ok { 26 | t.Errorf("%q: expected negative cycle", test.Name) 27 | } 28 | continue 29 | } 30 | if !ok { 31 | t.Fatalf("%q: unexpected negative cycle", test.Name) 32 | } 33 | 34 | if pt.From().ID() != test.Query.From().ID() { 35 | t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.Query.From().ID()) 36 | } 37 | 38 | p, weight := pt.To(test.Query.To()) 39 | if weight != test.Weight { 40 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 41 | test.Name, weight, test.Weight) 42 | } 43 | if weight := pt.WeightTo(test.Query.To()); weight != test.Weight { 44 | t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", 45 | test.Name, weight, test.Weight) 46 | } 47 | 48 | var got []int 49 | for _, n := range p { 50 | got = append(got, n.ID()) 51 | } 52 | ok = len(got) == 0 && len(test.WantPaths) == 0 53 | for _, sp := range test.WantPaths { 54 | if reflect.DeepEqual(got, sp) { 55 | ok = true 56 | break 57 | } 58 | } 59 | if !ok { 60 | t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", 61 | test.Name, p, test.WantPaths) 62 | } 63 | 64 | np, weight := pt.To(test.NoPathFor.To()) 65 | if pt.From().ID() == test.NoPathFor.From().ID() && (np != nil || !math.IsInf(weight, 1)) { 66 | t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f\nwant:path= weight=+Inf", 67 | test.Name, np, weight) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /path/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | "github.com/gonum/graph/graphs/gen" 13 | "github.com/gonum/graph/simple" 14 | ) 15 | 16 | var ( 17 | gnpUndirected_10_tenth = gnpUndirected(10, 0.1) 18 | gnpUndirected_100_tenth = gnpUndirected(100, 0.1) 19 | gnpUndirected_1000_tenth = gnpUndirected(1000, 0.1) 20 | gnpUndirected_10_half = gnpUndirected(10, 0.5) 21 | gnpUndirected_100_half = gnpUndirected(100, 0.5) 22 | gnpUndirected_1000_half = gnpUndirected(1000, 0.5) 23 | ) 24 | 25 | func gnpUndirected(n int, p float64) graph.Undirected { 26 | g := simple.NewUndirectedGraph(0, math.Inf(1)) 27 | gen.Gnp(g, n, p, nil) 28 | return g 29 | } 30 | 31 | func benchmarkAStarNilHeuristic(b *testing.B, g graph.Undirected) { 32 | var expanded int 33 | for i := 0; i < b.N; i++ { 34 | _, expanded = AStar(simple.Node(0), simple.Node(1), g, nil) 35 | } 36 | if expanded == 0 { 37 | b.Fatal("unexpected number of expanded nodes") 38 | } 39 | } 40 | 41 | func BenchmarkAStarGnp_10_tenth(b *testing.B) { 42 | benchmarkAStarNilHeuristic(b, gnpUndirected_10_tenth) 43 | } 44 | func BenchmarkAStarGnp_100_tenth(b *testing.B) { 45 | benchmarkAStarNilHeuristic(b, gnpUndirected_100_tenth) 46 | } 47 | func BenchmarkAStarGnp_1000_tenth(b *testing.B) { 48 | benchmarkAStarNilHeuristic(b, gnpUndirected_1000_tenth) 49 | } 50 | func BenchmarkAStarGnp_10_half(b *testing.B) { 51 | benchmarkAStarNilHeuristic(b, gnpUndirected_10_half) 52 | } 53 | func BenchmarkAStarGnp_100_half(b *testing.B) { 54 | benchmarkAStarNilHeuristic(b, gnpUndirected_100_half) 55 | } 56 | func BenchmarkAStarGnp_1000_half(b *testing.B) { 57 | benchmarkAStarNilHeuristic(b, gnpUndirected_1000_half) 58 | } 59 | 60 | var ( 61 | nswUndirected_10_2_2_2 = navigableSmallWorldUndirected(10, 2, 2, 2) 62 | nswUndirected_10_2_5_2 = navigableSmallWorldUndirected(10, 2, 5, 2) 63 | nswUndirected_100_5_10_2 = navigableSmallWorldUndirected(100, 5, 10, 2) 64 | nswUndirected_100_5_20_2 = navigableSmallWorldUndirected(100, 5, 20, 2) 65 | ) 66 | 67 | func navigableSmallWorldUndirected(n, p, q int, r float64) graph.Undirected { 68 | g := simple.NewUndirectedGraph(0, math.Inf(1)) 69 | gen.NavigableSmallWorld(g, []int{n, n}, p, q, r, nil) 70 | return g 71 | } 72 | 73 | func coordinatesForID(n graph.Node, c, r int) [2]int { 74 | id := n.ID() 75 | if id >= c*r { 76 | panic("out of range") 77 | } 78 | return [2]int{id / r, id % r} 79 | } 80 | 81 | // manhattanBetween returns the Manhattan distance between a and b. 82 | func manhattanBetween(a, b [2]int) float64 { 83 | var d int 84 | for i, v := range a { 85 | d += abs(v - b[i]) 86 | } 87 | return float64(d) 88 | } 89 | 90 | func abs(a int) int { 91 | if a < 0 { 92 | return -a 93 | } 94 | return a 95 | } 96 | 97 | func benchmarkAStarHeuristic(b *testing.B, g graph.Undirected, h Heuristic) { 98 | var expanded int 99 | for i := 0; i < b.N; i++ { 100 | _, expanded = AStar(simple.Node(0), simple.Node(1), g, h) 101 | } 102 | if expanded == 0 { 103 | b.Fatal("unexpected number of expanded nodes") 104 | } 105 | } 106 | 107 | func BenchmarkAStarUndirectedmallWorld_10_2_2_2(b *testing.B) { 108 | benchmarkAStarHeuristic(b, nswUndirected_10_2_2_2, nil) 109 | } 110 | func BenchmarkAStarUndirectedmallWorld_10_2_2_2_Heur(b *testing.B) { 111 | h := func(x, y graph.Node) float64 { 112 | return manhattanBetween(coordinatesForID(x, 10, 10), coordinatesForID(y, 10, 10)) 113 | } 114 | benchmarkAStarHeuristic(b, nswUndirected_10_2_2_2, h) 115 | } 116 | func BenchmarkAStarUndirectedmallWorld_10_2_5_2(b *testing.B) { 117 | benchmarkAStarHeuristic(b, nswUndirected_10_2_5_2, nil) 118 | } 119 | func BenchmarkAStarUndirectedmallWorld_10_2_5_2_Heur(b *testing.B) { 120 | h := func(x, y graph.Node) float64 { 121 | return manhattanBetween(coordinatesForID(x, 10, 10), coordinatesForID(y, 10, 10)) 122 | } 123 | benchmarkAStarHeuristic(b, nswUndirected_10_2_5_2, h) 124 | } 125 | func BenchmarkAStarUndirectedmallWorld_100_5_10_2(b *testing.B) { 126 | benchmarkAStarHeuristic(b, nswUndirected_100_5_10_2, nil) 127 | } 128 | func BenchmarkAStarUndirectedmallWorld_100_5_10_2_Heur(b *testing.B) { 129 | h := func(x, y graph.Node) float64 { 130 | return manhattanBetween(coordinatesForID(x, 100, 100), coordinatesForID(y, 100, 100)) 131 | } 132 | benchmarkAStarHeuristic(b, nswUndirected_100_5_10_2, h) 133 | } 134 | func BenchmarkAStarUndirectedmallWorld_100_5_20_2(b *testing.B) { 135 | benchmarkAStarHeuristic(b, nswUndirected_100_5_20_2, nil) 136 | } 137 | func BenchmarkAStarUndirectedmallWorld_100_5_20_2_Heur(b *testing.B) { 138 | h := func(x, y graph.Node) float64 { 139 | return manhattanBetween(coordinatesForID(x, 100, 100), coordinatesForID(y, 100, 100)) 140 | } 141 | benchmarkAStarHeuristic(b, nswUndirected_100_5_20_2, h) 142 | } 143 | -------------------------------------------------------------------------------- /path/control_flow.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "github.com/gonum/graph" 9 | "github.com/gonum/graph/internal/set" 10 | ) 11 | 12 | // Dominators returns all dominators for all nodes in g. It does not 13 | // prune for strict post-dominators, immediate dominators etc. 14 | // 15 | // A dominates B if and only if the only path through B travels through A. 16 | func Dominators(start graph.Node, g graph.Graph) map[int]set.Nodes { 17 | allNodes := make(set.Nodes) 18 | nlist := g.Nodes() 19 | dominators := make(map[int]set.Nodes, len(nlist)) 20 | for _, node := range nlist { 21 | allNodes.Add(node) 22 | } 23 | 24 | var to func(graph.Node) []graph.Node 25 | switch g := g.(type) { 26 | case graph.Directed: 27 | to = g.To 28 | default: 29 | to = g.From 30 | } 31 | 32 | for _, node := range nlist { 33 | dominators[node.ID()] = make(set.Nodes) 34 | if node.ID() == start.ID() { 35 | dominators[node.ID()].Add(start) 36 | } else { 37 | dominators[node.ID()].Copy(allNodes) 38 | } 39 | } 40 | 41 | for somethingChanged := true; somethingChanged; { 42 | somethingChanged = false 43 | for _, node := range nlist { 44 | if node.ID() == start.ID() { 45 | continue 46 | } 47 | preds := to(node) 48 | if len(preds) == 0 { 49 | continue 50 | } 51 | tmp := make(set.Nodes).Copy(dominators[preds[0].ID()]) 52 | for _, pred := range preds[1:] { 53 | tmp.Intersect(tmp, dominators[pred.ID()]) 54 | } 55 | 56 | dom := make(set.Nodes) 57 | dom.Add(node) 58 | 59 | dom.Union(dom, tmp) 60 | if !set.Equal(dom, dominators[node.ID()]) { 61 | dominators[node.ID()] = dom 62 | somethingChanged = true 63 | } 64 | } 65 | } 66 | 67 | return dominators 68 | } 69 | 70 | // PostDominators returns all post-dominators for all nodes in g. It does not 71 | // prune for strict post-dominators, immediate post-dominators etc. 72 | // 73 | // A post-dominates B if and only if all paths from B travel through A. 74 | func PostDominators(end graph.Node, g graph.Graph) map[int]set.Nodes { 75 | allNodes := make(set.Nodes) 76 | nlist := g.Nodes() 77 | dominators := make(map[int]set.Nodes, len(nlist)) 78 | for _, node := range nlist { 79 | allNodes.Add(node) 80 | } 81 | 82 | for _, node := range nlist { 83 | dominators[node.ID()] = make(set.Nodes) 84 | if node.ID() == end.ID() { 85 | dominators[node.ID()].Add(end) 86 | } else { 87 | dominators[node.ID()].Copy(allNodes) 88 | } 89 | } 90 | 91 | for somethingChanged := true; somethingChanged; { 92 | somethingChanged = false 93 | for _, node := range nlist { 94 | if node.ID() == end.ID() { 95 | continue 96 | } 97 | succs := g.From(node) 98 | if len(succs) == 0 { 99 | continue 100 | } 101 | tmp := make(set.Nodes).Copy(dominators[succs[0].ID()]) 102 | for _, succ := range succs[1:] { 103 | tmp.Intersect(tmp, dominators[succ.ID()]) 104 | } 105 | 106 | dom := make(set.Nodes) 107 | dom.Add(node) 108 | 109 | dom.Union(dom, tmp) 110 | if !set.Equal(dom, dominators[node.ID()]) { 111 | dominators[node.ID()] = dom 112 | somethingChanged = true 113 | } 114 | } 115 | } 116 | 117 | return dominators 118 | } 119 | -------------------------------------------------------------------------------- /path/dijkstra.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "container/heap" 9 | 10 | "github.com/gonum/graph" 11 | ) 12 | 13 | // DijkstraFrom returns a shortest-path tree for a shortest path from u to all nodes in 14 | // the graph g. If the graph does not implement graph.Weighter, UniformCost is used. 15 | // DijkstraFrom will panic if g has a u-reachable negative edge weight. 16 | // 17 | // The time complexity of DijkstrFrom is O(|E|.log|V|). 18 | func DijkstraFrom(u graph.Node, g graph.Graph) Shortest { 19 | if !g.Has(u) { 20 | return Shortest{from: u} 21 | } 22 | var weight Weighting 23 | if wg, ok := g.(graph.Weighter); ok { 24 | weight = wg.Weight 25 | } else { 26 | weight = UniformCost(g) 27 | } 28 | 29 | nodes := g.Nodes() 30 | path := newShortestFrom(u, nodes) 31 | 32 | // Dijkstra's algorithm here is implemented essentially as 33 | // described in Function B.2 in figure 6 of UTCS Technical 34 | // Report TR-07-54. 35 | // 36 | // This implementation deviates from the report as follows: 37 | // - the value of path.dist for the start vertex u is initialized to 0; 38 | // - outdated elements from the priority queue (i.e. with respect to the dist value) 39 | // are skipped. 40 | // 41 | // http://www.cs.utexas.edu/ftp/techreports/tr07-54.pdf 42 | Q := priorityQueue{{node: u, dist: 0}} 43 | for Q.Len() != 0 { 44 | mid := heap.Pop(&Q).(distanceNode) 45 | k := path.indexOf[mid.node.ID()] 46 | if mid.dist > path.dist[k] { 47 | continue 48 | } 49 | for _, v := range g.From(mid.node) { 50 | j := path.indexOf[v.ID()] 51 | w, ok := weight(mid.node, v) 52 | if !ok { 53 | panic("dijkstra: unexpected invalid weight") 54 | } 55 | if w < 0 { 56 | panic("dijkstra: negative edge weight") 57 | } 58 | joint := path.dist[k] + w 59 | if joint < path.dist[j] { 60 | heap.Push(&Q, distanceNode{node: v, dist: joint}) 61 | path.set(j, joint, k) 62 | } 63 | } 64 | } 65 | 66 | return path 67 | } 68 | 69 | // DijkstraAllPaths returns a shortest-path tree for shortest paths in the graph g. 70 | // If the graph does not implement graph.Weighter, UniformCost is used. 71 | // DijkstraAllPaths will panic if g has a negative edge weight. 72 | // 73 | // The time complexity of DijkstrAllPaths is O(|V|.|E|+|V|^2.log|V|). 74 | func DijkstraAllPaths(g graph.Graph) (paths AllShortest) { 75 | paths = newAllShortest(g.Nodes(), false) 76 | dijkstraAllPaths(g, paths) 77 | return paths 78 | } 79 | 80 | // dijkstraAllPaths is the all-paths implementation of Dijkstra. It is shared 81 | // between DijkstraAllPaths and JohnsonAllPaths to avoid repeated allocation 82 | // of the nodes slice and the indexOf map. It returns nothing, but stores the 83 | // result of the work in the paths parameter which is a reference type. 84 | func dijkstraAllPaths(g graph.Graph, paths AllShortest) { 85 | var weight Weighting 86 | if wg, ok := g.(graph.Weighter); ok { 87 | weight = wg.Weight 88 | } else { 89 | weight = UniformCost(g) 90 | } 91 | 92 | var Q priorityQueue 93 | for i, u := range paths.nodes { 94 | // Dijkstra's algorithm here is implemented essentially as 95 | // described in Function B.2 in figure 6 of UTCS Technical 96 | // Report TR-07-54 with the addition of handling multiple 97 | // co-equal paths. 98 | // 99 | // http://www.cs.utexas.edu/ftp/techreports/tr07-54.pdf 100 | 101 | // Q must be empty at this point. 102 | heap.Push(&Q, distanceNode{node: u, dist: 0}) 103 | for Q.Len() != 0 { 104 | mid := heap.Pop(&Q).(distanceNode) 105 | k := paths.indexOf[mid.node.ID()] 106 | if mid.dist < paths.dist.At(i, k) { 107 | paths.dist.Set(i, k, mid.dist) 108 | } 109 | for _, v := range g.From(mid.node) { 110 | j := paths.indexOf[v.ID()] 111 | w, ok := weight(mid.node, v) 112 | if !ok { 113 | panic("dijkstra: unexpected invalid weight") 114 | } 115 | if w < 0 { 116 | panic("dijkstra: negative edge weight") 117 | } 118 | joint := paths.dist.At(i, k) + w 119 | if joint < paths.dist.At(i, j) { 120 | heap.Push(&Q, distanceNode{node: v, dist: joint}) 121 | paths.set(i, j, joint, k) 122 | } else if joint == paths.dist.At(i, j) { 123 | paths.add(i, j, k) 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | type distanceNode struct { 131 | node graph.Node 132 | dist float64 133 | } 134 | 135 | // priorityQueue implements a no-dec priority queue. 136 | type priorityQueue []distanceNode 137 | 138 | func (q priorityQueue) Len() int { return len(q) } 139 | func (q priorityQueue) Less(i, j int) bool { return q[i].dist < q[j].dist } 140 | func (q priorityQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] } 141 | func (q *priorityQueue) Push(n interface{}) { *q = append(*q, n.(distanceNode)) } 142 | func (q *priorityQueue) Pop() interface{} { 143 | t := *q 144 | var n interface{} 145 | n, *q = t[len(t)-1], t[:len(t)-1] 146 | return n 147 | } 148 | -------------------------------------------------------------------------------- /path/dijkstra_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/graph" 14 | "github.com/gonum/graph/internal/ordered" 15 | "github.com/gonum/graph/path/internal/testgraphs" 16 | ) 17 | 18 | func TestDijkstraFrom(t *testing.T) { 19 | for _, test := range testgraphs.ShortestPathTests { 20 | g := test.Graph() 21 | for _, e := range test.Edges { 22 | g.SetEdge(e) 23 | } 24 | 25 | var ( 26 | pt Shortest 27 | 28 | panicked bool 29 | ) 30 | func() { 31 | defer func() { 32 | panicked = recover() != nil 33 | }() 34 | pt = DijkstraFrom(test.Query.From(), g.(graph.Graph)) 35 | }() 36 | if panicked || test.HasNegativeWeight { 37 | if !test.HasNegativeWeight { 38 | t.Errorf("%q: unexpected panic", test.Name) 39 | } 40 | if !panicked { 41 | t.Errorf("%q: expected panic for negative edge weight", test.Name) 42 | } 43 | continue 44 | } 45 | 46 | if pt.From().ID() != test.Query.From().ID() { 47 | t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.Query.From().ID()) 48 | } 49 | 50 | p, weight := pt.To(test.Query.To()) 51 | if weight != test.Weight { 52 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 53 | test.Name, weight, test.Weight) 54 | } 55 | if weight := pt.WeightTo(test.Query.To()); weight != test.Weight { 56 | t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", 57 | test.Name, weight, test.Weight) 58 | } 59 | 60 | var got []int 61 | for _, n := range p { 62 | got = append(got, n.ID()) 63 | } 64 | ok := len(got) == 0 && len(test.WantPaths) == 0 65 | for _, sp := range test.WantPaths { 66 | if reflect.DeepEqual(got, sp) { 67 | ok = true 68 | break 69 | } 70 | } 71 | if !ok { 72 | t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", 73 | test.Name, p, test.WantPaths) 74 | } 75 | 76 | np, weight := pt.To(test.NoPathFor.To()) 77 | if pt.From().ID() == test.NoPathFor.From().ID() && (np != nil || !math.IsInf(weight, 1)) { 78 | t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f\nwant:path= weight=+Inf", 79 | test.Name, np, weight) 80 | } 81 | } 82 | } 83 | 84 | func TestDijkstraAllPaths(t *testing.T) { 85 | for _, test := range testgraphs.ShortestPathTests { 86 | g := test.Graph() 87 | for _, e := range test.Edges { 88 | g.SetEdge(e) 89 | } 90 | 91 | var ( 92 | pt AllShortest 93 | 94 | panicked bool 95 | ) 96 | func() { 97 | defer func() { 98 | panicked = recover() != nil 99 | }() 100 | pt = DijkstraAllPaths(g.(graph.Graph)) 101 | }() 102 | if panicked || test.HasNegativeWeight { 103 | if !test.HasNegativeWeight { 104 | t.Errorf("%q: unexpected panic", test.Name) 105 | } 106 | if !panicked { 107 | t.Errorf("%q: expected panic for negative edge weight", test.Name) 108 | } 109 | continue 110 | } 111 | 112 | // Check all random paths returned are OK. 113 | for i := 0; i < 10; i++ { 114 | p, weight, unique := pt.Between(test.Query.From(), test.Query.To()) 115 | if weight != test.Weight { 116 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 117 | test.Name, weight, test.Weight) 118 | } 119 | if weight := pt.Weight(test.Query.From(), test.Query.To()); weight != test.Weight { 120 | t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", 121 | test.Name, weight, test.Weight) 122 | } 123 | if unique != test.HasUniquePath { 124 | t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t", 125 | test.Name, unique, test.HasUniquePath) 126 | } 127 | 128 | var got []int 129 | for _, n := range p { 130 | got = append(got, n.ID()) 131 | } 132 | ok := len(got) == 0 && len(test.WantPaths) == 0 133 | for _, sp := range test.WantPaths { 134 | if reflect.DeepEqual(got, sp) { 135 | ok = true 136 | break 137 | } 138 | } 139 | if !ok { 140 | t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", 141 | test.Name, p, test.WantPaths) 142 | } 143 | } 144 | 145 | np, weight, unique := pt.Between(test.NoPathFor.From(), test.NoPathFor.To()) 146 | if np != nil || !math.IsInf(weight, 1) || unique != false { 147 | t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", 148 | test.Name, np, weight, unique) 149 | } 150 | 151 | paths, weight := pt.AllBetween(test.Query.From(), test.Query.To()) 152 | if weight != test.Weight { 153 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 154 | test.Name, weight, test.Weight) 155 | } 156 | 157 | var got [][]int 158 | if len(paths) != 0 { 159 | got = make([][]int, len(paths)) 160 | } 161 | for i, p := range paths { 162 | for _, v := range p { 163 | got[i] = append(got[i], v.ID()) 164 | } 165 | } 166 | sort.Sort(ordered.BySliceValues(got)) 167 | if !reflect.DeepEqual(got, test.WantPaths) { 168 | t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v", 169 | test.Name, got, test.WantPaths) 170 | } 171 | 172 | nps, weight := pt.AllBetween(test.NoPathFor.From(), test.NoPathFor.To()) 173 | if nps != nil || !math.IsInf(weight, 1) { 174 | t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path= weight=+Inf", 175 | test.Name, nps, weight) 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /path/disjoint.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | // A disjoint set is a collection of non-overlapping sets. That is, for any two sets in the 8 | // disjoint set, their intersection is the empty set. 9 | // 10 | // A disjoint set has three principle operations: Make Set, Find, and Union. 11 | // 12 | // Make set creates a new set for an element (presuming it does not already exist in any set in 13 | // the disjoint set), Find finds the set containing that element (if any), and Union merges two 14 | // sets in the disjoint set. In general, algorithms operating on disjoint sets are "union-find" 15 | // algorithms, where two sets are found with Find, and then joined with Union. 16 | // 17 | // A concrete example of a union-find algorithm can be found as discrete.Kruskal -- which unions 18 | // two sets when an edge is created between two vertices, and refuses to make an edge between two 19 | // vertices if they're part of the same set. 20 | type disjointSet struct { 21 | master map[int]*disjointSetNode 22 | } 23 | 24 | type disjointSetNode struct { 25 | parent *disjointSetNode 26 | rank int 27 | } 28 | 29 | func newDisjointSet() *disjointSet { 30 | return &disjointSet{master: make(map[int]*disjointSetNode)} 31 | } 32 | 33 | // If the element isn't already somewhere in there, adds it to the master set and its own tiny set. 34 | func (ds *disjointSet) makeSet(e int) { 35 | if _, ok := ds.master[e]; ok { 36 | return 37 | } 38 | dsNode := &disjointSetNode{rank: 0} 39 | dsNode.parent = dsNode 40 | ds.master[e] = dsNode 41 | } 42 | 43 | // Returns the set the element belongs to, or nil if none. 44 | func (ds *disjointSet) find(e int) *disjointSetNode { 45 | dsNode, ok := ds.master[e] 46 | if !ok { 47 | return nil 48 | } 49 | 50 | return find(dsNode) 51 | } 52 | 53 | func find(dsNode *disjointSetNode) *disjointSetNode { 54 | if dsNode.parent != dsNode { 55 | dsNode.parent = find(dsNode.parent) 56 | } 57 | 58 | return dsNode.parent 59 | } 60 | 61 | // Unions two subsets within the disjointSet. 62 | // 63 | // If x or y are not in this disjoint set, the behavior is undefined. If either pointer is nil, 64 | // this function will panic. 65 | func (ds *disjointSet) union(x, y *disjointSetNode) { 66 | if x == nil || y == nil { 67 | panic("Disjoint Set union on nil sets") 68 | } 69 | xRoot := find(x) 70 | yRoot := find(y) 71 | if xRoot == nil || yRoot == nil { 72 | return 73 | } 74 | 75 | if xRoot == yRoot { 76 | return 77 | } 78 | 79 | if xRoot.rank < yRoot.rank { 80 | xRoot.parent = yRoot 81 | } else if yRoot.rank < xRoot.rank { 82 | yRoot.parent = xRoot 83 | } else { 84 | yRoot.parent = xRoot 85 | xRoot.rank++ 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /path/disjoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestDisjointSetMakeSet(t *testing.T) { 12 | ds := newDisjointSet() 13 | if ds.master == nil { 14 | t.Fatal("Internal disjoint set map erroneously nil") 15 | } else if len(ds.master) != 0 { 16 | t.Error("Disjoint set master map of wrong size") 17 | } 18 | 19 | ds.makeSet(3) 20 | if len(ds.master) != 1 { 21 | t.Error("Disjoint set master map of wrong size") 22 | } 23 | 24 | if node, ok := ds.master[3]; !ok { 25 | t.Error("Make set did not successfully add element") 26 | } else { 27 | if node == nil { 28 | t.Fatal("Disjoint set node from makeSet is nil") 29 | } 30 | 31 | if node.rank != 0 { 32 | t.Error("Node rank set incorrectly") 33 | } 34 | 35 | if node.parent != node { 36 | t.Error("Node parent set incorrectly") 37 | } 38 | } 39 | } 40 | 41 | func TestDisjointSetFind(t *testing.T) { 42 | ds := newDisjointSet() 43 | 44 | ds.makeSet(3) 45 | ds.makeSet(5) 46 | 47 | if ds.find(3) == ds.find(5) { 48 | t.Error("Disjoint sets incorrectly found to be the same") 49 | } 50 | } 51 | 52 | func TestUnion(t *testing.T) { 53 | ds := newDisjointSet() 54 | 55 | ds.makeSet(3) 56 | ds.makeSet(5) 57 | 58 | ds.union(ds.find(3), ds.find(5)) 59 | 60 | if ds.find(3) != ds.find(5) { 61 | t.Error("Sets found to be disjoint after union") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /path/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package path provides graph path finding functions. 9 | package path 10 | -------------------------------------------------------------------------------- /path/dynamic/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package dynamic provides incremental heuristic graph path finding functions. 9 | package dynamic 10 | -------------------------------------------------------------------------------- /path/dynamic/dumper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dynamic 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "sort" 12 | "text/tabwriter" 13 | 14 | "github.com/gonum/graph/path/internal" 15 | "github.com/gonum/graph/simple" 16 | ) 17 | 18 | // dumper implements a grid D* Lite statistics dump. 19 | type dumper struct { 20 | step int 21 | 22 | dStarLite *DStarLite 23 | grid *internal.LimitedVisionGrid 24 | 25 | w io.Writer 26 | } 27 | 28 | // dump writes a single step of a D* Lite path search to the dumper's io.Writer. 29 | func (d *dumper) dump(withpath bool) { 30 | if d == nil { 31 | return 32 | } 33 | var pathStep map[int]int 34 | if withpath { 35 | pathStep = make(map[int]int) 36 | path, _ := d.dStarLite.Path() 37 | for i, n := range path { 38 | pathStep[n.ID()] = i 39 | } 40 | } 41 | fmt.Fprintf(d.w, "Step:%d kₘ=%v\n", d.step, d.dStarLite.keyModifier) 42 | d.step++ 43 | w := tabwriter.NewWriter(d.w, 0, 0, 0, ' ', tabwriter.Debug) 44 | rows, cols := d.grid.Grid.Dims() 45 | for r := 0; r < rows; r++ { 46 | if r == 0 { 47 | for c := 0; c < cols; c++ { 48 | if c != 0 { 49 | fmt.Fprint(w, "\t") 50 | } 51 | fmt.Fprint(w, "-------------------") 52 | } 53 | fmt.Fprintln(w) 54 | } 55 | for ln := 0; ln < 6; ln++ { 56 | for c := 0; c < cols; c++ { 57 | if c != 0 { 58 | fmt.Fprint(w, "\t") 59 | } 60 | n := d.dStarLite.model.Node(d.grid.NodeAt(r, c).ID()).(*dStarLiteNode) 61 | switch ln { 62 | case 0: 63 | if n.ID() == d.grid.Location.ID() { 64 | if d.grid.Grid.HasOpen(n) { 65 | fmt.Fprintf(w, "id:%2d >@<", n.ID()) 66 | } else { 67 | // Mark location as illegal. 68 | fmt.Fprintf(w, "id:%2d >!<", n.ID()) 69 | } 70 | } else if n.ID() == d.dStarLite.t.ID() { 71 | fmt.Fprintf(w, "id:%2d G", n.ID()) 72 | // Mark goal cell as illegal. 73 | if !d.grid.Grid.HasOpen(n) { 74 | fmt.Fprint(w, "!") 75 | } 76 | } else if pathStep[n.ID()] > 0 { 77 | fmt.Fprintf(w, "id:%2d %2d", n.ID(), pathStep[n.ID()]) 78 | // Mark path cells with an obstruction. 79 | if !d.grid.Grid.HasOpen(n) { 80 | fmt.Fprint(w, "!") 81 | } 82 | } else { 83 | fmt.Fprintf(w, "id:%2d", n.ID()) 84 | // Mark cells with an obstruction. 85 | if !d.grid.Grid.HasOpen(n) { 86 | fmt.Fprint(w, " *") 87 | } 88 | } 89 | case 1: 90 | fmt.Fprintf(w, "h: %.4v", d.dStarLite.heuristic(n, d.dStarLite.Here())) 91 | case 2: 92 | fmt.Fprintf(w, "g: %.4v", n.g) 93 | case 3: 94 | fmt.Fprintf(w, "rhs:%.4v", n.rhs) 95 | case 4: 96 | if n.g != n.rhs { 97 | fmt.Fprintf(w, "key:%.3f", n.key) 98 | } 99 | if n.key == n.key { 100 | // Mark keys for nodes in the priority queue. 101 | // We use NaN inequality for this check since all 102 | // keys not in the queue must have their key set 103 | // to badKey. 104 | // 105 | // This should always mark cells where key is 106 | // printed. 107 | fmt.Fprint(w, "*") 108 | } 109 | if n.g > n.rhs { 110 | fmt.Fprint(w, "^") 111 | } 112 | if n.g < n.rhs { 113 | fmt.Fprint(w, "v") 114 | } 115 | default: 116 | fmt.Fprint(w, "-------------------") 117 | } 118 | } 119 | fmt.Fprintln(w) 120 | } 121 | } 122 | w.Flush() 123 | fmt.Fprintln(d.w) 124 | } 125 | 126 | // printEdges pretty prints the given edges to the dumper's io.Writer using the provided 127 | // format string. The edges are first formated to a string, so the format string must use 128 | // the %s verb to indicate where the edges are to be printed. 129 | func (d *dumper) printEdges(format string, edges []simple.Edge) { 130 | if d == nil { 131 | return 132 | } 133 | var buf bytes.Buffer 134 | sort.Sort(lexically(edges)) 135 | for i, e := range edges { 136 | if i != 0 { 137 | fmt.Fprint(&buf, ", ") 138 | } 139 | fmt.Fprintf(&buf, "%d->%d:%.4v", e.From().ID(), e.To().ID(), e.Weight()) 140 | } 141 | if len(edges) == 0 { 142 | fmt.Fprint(&buf, "none") 143 | } 144 | fmt.Fprintf(d.w, format, buf.Bytes()) 145 | } 146 | 147 | type lexically []simple.Edge 148 | 149 | func (l lexically) Len() int { return len(l) } 150 | func (l lexically) Less(i, j int) bool { 151 | return l[i].From().ID() < l[j].From().ID() || (l[i].From().ID() == l[j].From().ID() && l[i].To().ID() < l[j].To().ID()) 152 | } 153 | func (l lexically) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 154 | -------------------------------------------------------------------------------- /path/floydwarshall.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import "github.com/gonum/graph" 8 | 9 | // FloydWarshall returns a shortest-path tree for the graph g or false indicating 10 | // that a negative cycle exists in the graph. If the graph does not implement 11 | // graph.Weighter, UniformCost is used. 12 | // 13 | // The time complexity of FloydWarshall is O(|V|^3). 14 | func FloydWarshall(g graph.Graph) (paths AllShortest, ok bool) { 15 | var weight Weighting 16 | if wg, ok := g.(graph.Weighter); ok { 17 | weight = wg.Weight 18 | } else { 19 | weight = UniformCost(g) 20 | } 21 | 22 | nodes := g.Nodes() 23 | paths = newAllShortest(nodes, true) 24 | for i, u := range nodes { 25 | paths.dist.Set(i, i, 0) 26 | for _, v := range g.From(u) { 27 | j := paths.indexOf[v.ID()] 28 | w, ok := weight(u, v) 29 | if !ok { 30 | panic("floyd-warshall: unexpected invalid weight") 31 | } 32 | paths.set(i, j, w, j) 33 | } 34 | } 35 | 36 | for k := range nodes { 37 | for i := range nodes { 38 | for j := range nodes { 39 | ij := paths.dist.At(i, j) 40 | joint := paths.dist.At(i, k) + paths.dist.At(k, j) 41 | if ij > joint { 42 | paths.set(i, j, joint, paths.at(i, k)...) 43 | } else if ij-joint == 0 { 44 | paths.add(i, j, paths.at(i, k)...) 45 | } 46 | } 47 | } 48 | } 49 | 50 | ok = true 51 | for i := range nodes { 52 | if paths.dist.At(i, i) < 0 { 53 | ok = false 54 | break 55 | } 56 | } 57 | 58 | return paths, ok 59 | } 60 | -------------------------------------------------------------------------------- /path/floydwarshall_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/graph" 14 | "github.com/gonum/graph/internal/ordered" 15 | "github.com/gonum/graph/path/internal/testgraphs" 16 | ) 17 | 18 | func TestFloydWarshall(t *testing.T) { 19 | for _, test := range testgraphs.ShortestPathTests { 20 | g := test.Graph() 21 | for _, e := range test.Edges { 22 | g.SetEdge(e) 23 | } 24 | 25 | pt, ok := FloydWarshall(g.(graph.Graph)) 26 | if test.HasNegativeCycle { 27 | if ok { 28 | t.Errorf("%q: expected negative cycle", test.Name) 29 | } 30 | continue 31 | } 32 | if !ok { 33 | t.Fatalf("%q: unexpected negative cycle", test.Name) 34 | } 35 | 36 | // Check all random paths returned are OK. 37 | for i := 0; i < 10; i++ { 38 | p, weight, unique := pt.Between(test.Query.From(), test.Query.To()) 39 | if weight != test.Weight { 40 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 41 | test.Name, weight, test.Weight) 42 | } 43 | if weight := pt.Weight(test.Query.From(), test.Query.To()); weight != test.Weight { 44 | t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", 45 | test.Name, weight, test.Weight) 46 | } 47 | if unique != test.HasUniquePath { 48 | t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t", 49 | test.Name, unique, test.HasUniquePath) 50 | } 51 | 52 | var got []int 53 | for _, n := range p { 54 | got = append(got, n.ID()) 55 | } 56 | ok := len(got) == 0 && len(test.WantPaths) == 0 57 | for _, sp := range test.WantPaths { 58 | if reflect.DeepEqual(got, sp) { 59 | ok = true 60 | break 61 | } 62 | } 63 | if !ok { 64 | t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", 65 | test.Name, p, test.WantPaths) 66 | } 67 | } 68 | 69 | np, weight, unique := pt.Between(test.NoPathFor.From(), test.NoPathFor.To()) 70 | if np != nil || !math.IsInf(weight, 1) || unique != false { 71 | t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", 72 | test.Name, np, weight, unique) 73 | } 74 | 75 | paths, weight := pt.AllBetween(test.Query.From(), test.Query.To()) 76 | if weight != test.Weight { 77 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 78 | test.Name, weight, test.Weight) 79 | } 80 | 81 | var got [][]int 82 | if len(paths) != 0 { 83 | got = make([][]int, len(paths)) 84 | } 85 | for i, p := range paths { 86 | for _, v := range p { 87 | got[i] = append(got[i], v.ID()) 88 | } 89 | } 90 | sort.Sort(ordered.BySliceValues(got)) 91 | if !reflect.DeepEqual(got, test.WantPaths) { 92 | t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v", 93 | test.Name, got, test.WantPaths) 94 | } 95 | 96 | nps, weight := pt.AllBetween(test.NoPathFor.From(), test.NoPathFor.To()) 97 | if nps != nil || !math.IsInf(weight, 1) { 98 | t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path= weight=+Inf", 99 | test.Name, nps, weight) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /path/internal/grid_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package internal 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "reflect" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/gonum/graph" 15 | "github.com/gonum/graph/simple" 16 | ) 17 | 18 | var _ graph.Graph = (*Grid)(nil) 19 | 20 | func join(g ...string) string { return strings.Join(g, "\n") } 21 | 22 | type node int 23 | 24 | func (n node) ID() int { return int(n) } 25 | 26 | func TestGrid(t *testing.T) { 27 | g := NewGrid(4, 4, false) 28 | 29 | got := g.String() 30 | want := join( 31 | "****", 32 | "****", 33 | "****", 34 | "****", 35 | ) 36 | if got != want { 37 | t.Fatalf("unexpected grid rendering:\ngot: %q\nwant:%q", got, want) 38 | } 39 | 40 | var ops = []struct { 41 | r, c int 42 | state bool 43 | want string 44 | }{ 45 | { 46 | r: 0, c: 1, 47 | state: true, 48 | want: join( 49 | "*.**", 50 | "****", 51 | "****", 52 | "****", 53 | ), 54 | }, 55 | { 56 | r: 0, c: 1, 57 | state: false, 58 | want: join( 59 | "****", 60 | "****", 61 | "****", 62 | "****", 63 | ), 64 | }, 65 | { 66 | r: 0, c: 1, 67 | state: true, 68 | want: join( 69 | "*.**", 70 | "****", 71 | "****", 72 | "****", 73 | ), 74 | }, 75 | { 76 | r: 0, c: 2, 77 | state: true, 78 | want: join( 79 | "*..*", 80 | "****", 81 | "****", 82 | "****", 83 | ), 84 | }, 85 | { 86 | r: 1, c: 2, 87 | state: true, 88 | want: join( 89 | "*..*", 90 | "**.*", 91 | "****", 92 | "****", 93 | ), 94 | }, 95 | { 96 | r: 2, c: 2, 97 | state: true, 98 | want: join( 99 | "*..*", 100 | "**.*", 101 | "**.*", 102 | "****", 103 | ), 104 | }, 105 | { 106 | r: 3, c: 2, 107 | state: true, 108 | want: join( 109 | "*..*", 110 | "**.*", 111 | "**.*", 112 | "**.*", 113 | ), 114 | }, 115 | } 116 | for _, test := range ops { 117 | g.Set(test.r, test.c, test.state) 118 | got := g.String() 119 | if got != test.want { 120 | t.Fatalf("unexpected grid rendering after set (%d, %d) open state to %t:\ngot: %q\nwant:%q", 121 | test.r, test.c, test.state, got, test.want) 122 | } 123 | } 124 | 125 | // Match the last state from the loop against the 126 | // explicit description of the grid. 127 | got = NewGridFrom( 128 | "*..*", 129 | "**.*", 130 | "**.*", 131 | "**.*", 132 | ).String() 133 | want = g.String() 134 | if got != want { 135 | t.Fatalf("unexpected grid rendering from NewGridFrom:\ngot: %q\nwant:%q", got, want) 136 | } 137 | 138 | var paths = []struct { 139 | path []graph.Node 140 | diagonal bool 141 | want string 142 | }{ 143 | { 144 | path: nil, 145 | diagonal: false, 146 | want: join( 147 | "*..*", 148 | "**.*", 149 | "**.*", 150 | "**.*", 151 | ), 152 | }, 153 | { 154 | path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, 155 | diagonal: false, 156 | want: join( 157 | "*So*", 158 | "**o*", 159 | "**o*", 160 | "**G*", 161 | ), 162 | }, 163 | { 164 | path: []graph.Node{node(1), node(6), node(10), node(14)}, 165 | diagonal: false, 166 | want: join( 167 | "*S.*", 168 | "**!*", 169 | "**.*", 170 | "**.*", 171 | ), 172 | }, 173 | { 174 | path: []graph.Node{node(1), node(6), node(10), node(14)}, 175 | diagonal: true, 176 | want: join( 177 | "*S.*", 178 | "**o*", 179 | "**o*", 180 | "**G*", 181 | ), 182 | }, 183 | { 184 | path: []graph.Node{node(1), node(5), node(9)}, 185 | diagonal: false, 186 | want: join( 187 | "*S.*", 188 | "*!.*", 189 | "**.*", 190 | "**.*", 191 | ), 192 | }, 193 | } 194 | for _, test := range paths { 195 | g.AllowDiagonal = test.diagonal 196 | got, err := g.Render(test.path) 197 | errored := err != nil 198 | if bytes.Contains(got, []byte{'!'}) != errored { 199 | t.Fatalf("unexpected error return: got:%v want:%v", err, errors.New("grid: not a path in graph")) 200 | } 201 | if string(got) != test.want { 202 | t.Fatalf("unexpected grid path rendering for %v:\ngot: %q\nwant:%q", test.path, got, want) 203 | } 204 | } 205 | 206 | var coords = []struct { 207 | r, c int 208 | id int 209 | }{ 210 | {r: 0, c: 0, id: 0}, 211 | {r: 0, c: 3, id: 3}, 212 | {r: 3, c: 0, id: 12}, 213 | {r: 3, c: 3, id: 15}, 214 | } 215 | for _, test := range coords { 216 | if id := g.NodeAt(test.r, test.c).ID(); id != test.id { 217 | t.Fatalf("unexpected ID for node at (%d, %d):\ngot: %d\nwant:%d", test.r, test.c, id, test.id) 218 | } 219 | if r, c := g.RowCol(test.id); r != test.r || c != test.c { 220 | t.Fatalf("unexpected row/col for node %d:\ngot: (%d, %d)\nwant:(%d, %d)", test.id, r, c, test.r, test.c) 221 | } 222 | } 223 | 224 | var reach = []struct { 225 | from graph.Node 226 | diagonal bool 227 | to []graph.Node 228 | }{ 229 | { 230 | from: node(0), 231 | diagonal: false, 232 | to: nil, 233 | }, 234 | { 235 | from: node(2), 236 | diagonal: false, 237 | to: []graph.Node{simple.Node(1), simple.Node(6)}, 238 | }, 239 | { 240 | from: node(1), 241 | diagonal: false, 242 | to: []graph.Node{simple.Node(2)}, 243 | }, 244 | { 245 | from: node(1), 246 | diagonal: true, 247 | to: []graph.Node{simple.Node(2), simple.Node(6)}, 248 | }, 249 | } 250 | for _, test := range reach { 251 | g.AllowDiagonal = test.diagonal 252 | got := g.From(test.from) 253 | if !reflect.DeepEqual(got, test.to) { 254 | t.Fatalf("unexpected nodes from %d with allow diagonal=%t:\ngot: %v\nwant:%v", 255 | test.from, test.diagonal, got, test.to) 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /path/johnson_apsp.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "math" 9 | "math/rand" 10 | 11 | "github.com/gonum/graph" 12 | "github.com/gonum/graph/simple" 13 | ) 14 | 15 | // JohnsonAllPaths returns a shortest-path tree for shortest paths in the graph g. 16 | // If the graph does not implement graph.Weighter, UniformCost is used. 17 | // 18 | // The time complexity of JohnsonAllPaths is O(|V|.|E|+|V|^2.log|V|). 19 | func JohnsonAllPaths(g graph.Graph) (paths AllShortest, ok bool) { 20 | jg := johnsonWeightAdjuster{ 21 | g: g, 22 | from: g.From, 23 | edgeTo: g.Edge, 24 | } 25 | if wg, ok := g.(graph.Weighter); ok { 26 | jg.weight = wg.Weight 27 | } else { 28 | jg.weight = UniformCost(g) 29 | } 30 | 31 | paths = newAllShortest(g.Nodes(), false) 32 | 33 | sign := -1 34 | for { 35 | // Choose a random node ID until we find 36 | // one that is not in g. 37 | jg.q = sign * rand.Int() 38 | if _, exists := paths.indexOf[jg.q]; !exists { 39 | break 40 | } 41 | sign *= -1 42 | } 43 | 44 | jg.bellmanFord = true 45 | jg.adjustBy, ok = BellmanFordFrom(johnsonGraphNode(jg.q), jg) 46 | if !ok { 47 | return paths, false 48 | } 49 | 50 | jg.bellmanFord = false 51 | dijkstraAllPaths(jg, paths) 52 | 53 | for i, u := range paths.nodes { 54 | hu := jg.adjustBy.WeightTo(u) 55 | for j, v := range paths.nodes { 56 | if i == j { 57 | continue 58 | } 59 | hv := jg.adjustBy.WeightTo(v) 60 | paths.dist.Set(i, j, paths.dist.At(i, j)-hu+hv) 61 | } 62 | } 63 | 64 | return paths, ok 65 | } 66 | 67 | type johnsonWeightAdjuster struct { 68 | q int 69 | g graph.Graph 70 | 71 | from func(graph.Node) []graph.Node 72 | edgeTo func(graph.Node, graph.Node) graph.Edge 73 | weight Weighting 74 | 75 | bellmanFord bool 76 | adjustBy Shortest 77 | } 78 | 79 | var ( 80 | // johnsonWeightAdjuster has the behaviour 81 | // of a directed graph, but we don't need 82 | // to be explicit with the type since it 83 | // is not exported. 84 | _ graph.Graph = johnsonWeightAdjuster{} 85 | _ graph.Weighter = johnsonWeightAdjuster{} 86 | ) 87 | 88 | func (g johnsonWeightAdjuster) Has(n graph.Node) bool { 89 | if g.bellmanFord && n.ID() == g.q { 90 | return true 91 | } 92 | return g.g.Has(n) 93 | 94 | } 95 | 96 | func (g johnsonWeightAdjuster) Nodes() []graph.Node { 97 | if g.bellmanFord { 98 | return append(g.g.Nodes(), johnsonGraphNode(g.q)) 99 | } 100 | return g.g.Nodes() 101 | } 102 | 103 | func (g johnsonWeightAdjuster) From(n graph.Node) []graph.Node { 104 | if g.bellmanFord && n.ID() == g.q { 105 | return g.g.Nodes() 106 | } 107 | return g.from(n) 108 | } 109 | 110 | func (g johnsonWeightAdjuster) Edge(u, v graph.Node) graph.Edge { 111 | if g.bellmanFord && u.ID() == g.q && g.g.Has(v) { 112 | return simple.Edge{F: johnsonGraphNode(g.q), T: v} 113 | } 114 | return g.edgeTo(u, v) 115 | } 116 | 117 | func (g johnsonWeightAdjuster) Weight(x, y graph.Node) (w float64, ok bool) { 118 | if g.bellmanFord { 119 | switch g.q { 120 | case x.ID(): 121 | return 0, true 122 | case y.ID(): 123 | return math.Inf(1), false 124 | default: 125 | return g.weight(x, y) 126 | } 127 | } 128 | w, ok = g.weight(x, y) 129 | return w + g.adjustBy.WeightTo(x) - g.adjustBy.WeightTo(y), ok 130 | } 131 | 132 | func (johnsonWeightAdjuster) HasEdgeBetween(_, _ graph.Node) bool { 133 | panic("path: unintended use of johnsonWeightAdjuster") 134 | } 135 | 136 | type johnsonGraphNode int 137 | 138 | func (n johnsonGraphNode) ID() int { return int(n) } 139 | -------------------------------------------------------------------------------- /path/johnson_apsp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/graph" 14 | "github.com/gonum/graph/internal/ordered" 15 | "github.com/gonum/graph/path/internal/testgraphs" 16 | ) 17 | 18 | func TestJohnsonAllPaths(t *testing.T) { 19 | for _, test := range testgraphs.ShortestPathTests { 20 | g := test.Graph() 21 | for _, e := range test.Edges { 22 | g.SetEdge(e) 23 | } 24 | 25 | pt, ok := JohnsonAllPaths(g.(graph.Graph)) 26 | if test.HasNegativeCycle { 27 | if ok { 28 | t.Errorf("%q: expected negative cycle", test.Name) 29 | } 30 | continue 31 | } 32 | if !ok { 33 | t.Fatalf("%q: unexpected negative cycle", test.Name) 34 | } 35 | 36 | // Check all random paths returned are OK. 37 | for i := 0; i < 10; i++ { 38 | p, weight, unique := pt.Between(test.Query.From(), test.Query.To()) 39 | if weight != test.Weight { 40 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 41 | test.Name, weight, test.Weight) 42 | } 43 | if weight := pt.Weight(test.Query.From(), test.Query.To()); weight != test.Weight { 44 | t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", 45 | test.Name, weight, test.Weight) 46 | } 47 | if unique != test.HasUniquePath { 48 | t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t", 49 | test.Name, unique, test.HasUniquePath) 50 | } 51 | 52 | var got []int 53 | for _, n := range p { 54 | got = append(got, n.ID()) 55 | } 56 | ok := len(got) == 0 && len(test.WantPaths) == 0 57 | for _, sp := range test.WantPaths { 58 | if reflect.DeepEqual(got, sp) { 59 | ok = true 60 | break 61 | } 62 | } 63 | if !ok { 64 | t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", 65 | test.Name, p, test.WantPaths) 66 | } 67 | } 68 | 69 | np, weight, unique := pt.Between(test.NoPathFor.From(), test.NoPathFor.To()) 70 | if np != nil || !math.IsInf(weight, 1) || unique != false { 71 | t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", 72 | test.Name, np, weight, unique) 73 | } 74 | 75 | paths, weight := pt.AllBetween(test.Query.From(), test.Query.To()) 76 | if weight != test.Weight { 77 | t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 78 | test.Name, weight, test.Weight) 79 | } 80 | 81 | var got [][]int 82 | if len(paths) != 0 { 83 | got = make([][]int, len(paths)) 84 | } 85 | for i, p := range paths { 86 | for _, v := range p { 87 | got[i] = append(got[i], v.ID()) 88 | } 89 | } 90 | sort.Sort(ordered.BySliceValues(got)) 91 | if !reflect.DeepEqual(got, test.WantPaths) { 92 | t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v", 93 | test.Name, got, test.WantPaths) 94 | } 95 | 96 | nps, weight := pt.AllBetween(test.NoPathFor.From(), test.NoPathFor.To()) 97 | if nps != nil || !math.IsInf(weight, 1) { 98 | t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path= weight=+Inf", 99 | test.Name, nps, weight) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /path/spanning_tree.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "container/heap" 9 | "math" 10 | "sort" 11 | 12 | "github.com/gonum/graph" 13 | "github.com/gonum/graph/simple" 14 | ) 15 | 16 | // UndirectedWeighter is an undirected graph that returns edge weights. 17 | type UndirectedWeighter interface { 18 | graph.Undirected 19 | graph.Weighter 20 | } 21 | 22 | // Prim generates a minimum spanning tree of g by greedy tree extension, placing 23 | // the result in the destination, dst. If the edge weights of g are distinct 24 | // it will be the unique minimum spanning tree of g. The destination is not cleared 25 | // first. The weight of the minimum spanning tree is returned. If g is not connected, 26 | // a minimum spanning forest will be constructed in dst and the sum of minimum 27 | // spanning tree weights will be returned. 28 | func Prim(dst graph.UndirectedBuilder, g UndirectedWeighter) float64 { 29 | nodes := g.Nodes() 30 | if len(nodes) == 0 { 31 | return 0 32 | } 33 | 34 | q := &primQueue{ 35 | indexOf: make(map[int]int, len(nodes)-1), 36 | nodes: make([]simple.Edge, 0, len(nodes)-1), 37 | } 38 | for _, u := range nodes[1:] { 39 | heap.Push(q, simple.Edge{F: u, W: math.Inf(1)}) 40 | } 41 | 42 | u := nodes[0] 43 | for _, v := range g.From(u) { 44 | w, ok := g.Weight(u, v) 45 | if !ok { 46 | panic("prim: unexpected invalid weight") 47 | } 48 | q.update(v, u, w) 49 | } 50 | 51 | var w float64 52 | for q.Len() > 0 { 53 | e := heap.Pop(q).(simple.Edge) 54 | if e.To() != nil && g.HasEdgeBetween(e.From(), e.To()) { 55 | dst.SetEdge(e) 56 | w += e.Weight() 57 | } 58 | 59 | u = e.From() 60 | for _, n := range g.From(u) { 61 | if key, ok := q.key(n); ok { 62 | w, ok := g.Weight(u, n) 63 | if !ok { 64 | panic("prim: unexpected invalid weight") 65 | } 66 | if w < key { 67 | q.update(n, u, w) 68 | } 69 | } 70 | } 71 | } 72 | return w 73 | } 74 | 75 | // primQueue is a Prim's priority queue. The priority queue is a 76 | // queue of edge From nodes keyed on the minimum edge weight to 77 | // a node in the set of nodes already connected to the minimum 78 | // spanning forest. 79 | type primQueue struct { 80 | indexOf map[int]int 81 | nodes []simple.Edge 82 | } 83 | 84 | func (q *primQueue) Less(i, j int) bool { 85 | return q.nodes[i].Weight() < q.nodes[j].Weight() 86 | } 87 | 88 | func (q *primQueue) Swap(i, j int) { 89 | q.indexOf[q.nodes[i].From().ID()] = j 90 | q.indexOf[q.nodes[j].From().ID()] = i 91 | q.nodes[i], q.nodes[j] = q.nodes[j], q.nodes[i] 92 | } 93 | 94 | func (q *primQueue) Len() int { 95 | return len(q.nodes) 96 | } 97 | 98 | func (q *primQueue) Push(x interface{}) { 99 | n := x.(simple.Edge) 100 | q.indexOf[n.From().ID()] = len(q.nodes) 101 | q.nodes = append(q.nodes, n) 102 | } 103 | 104 | func (q *primQueue) Pop() interface{} { 105 | n := q.nodes[len(q.nodes)-1] 106 | q.nodes = q.nodes[:len(q.nodes)-1] 107 | delete(q.indexOf, n.From().ID()) 108 | return n 109 | } 110 | 111 | // key returns the key for the node u and whether the node is 112 | // in the queue. If the node is not in the queue, key is returned 113 | // as +Inf. 114 | func (q *primQueue) key(u graph.Node) (key float64, ok bool) { 115 | i, ok := q.indexOf[u.ID()] 116 | if !ok { 117 | return math.Inf(1), false 118 | } 119 | return q.nodes[i].Weight(), ok 120 | } 121 | 122 | // update updates u's position in the queue with the new closest 123 | // MST-connected neighbour, v, and the key weight between u and v. 124 | func (q *primQueue) update(u, v graph.Node, key float64) { 125 | id := u.ID() 126 | i, ok := q.indexOf[id] 127 | if !ok { 128 | return 129 | } 130 | q.nodes[i].T = v 131 | q.nodes[i].W = key 132 | heap.Fix(q, i) 133 | } 134 | 135 | // UndirectedWeightLister is an undirected graph that returns edge weights and 136 | // the set of edges in the graph. 137 | type UndirectedWeightLister interface { 138 | UndirectedWeighter 139 | Edges() []graph.Edge 140 | } 141 | 142 | // Kruskal generates a minimum spanning tree of g by greedy tree coalescence, placing 143 | // the result in the destination, dst. If the edge weights of g are distinct 144 | // it will be the unique minimum spanning tree of g. The destination is not cleared 145 | // first. The weight of the minimum spanning tree is returned. If g is not connected, 146 | // a minimum spanning forest will be constructed in dst and the sum of minimum 147 | // spanning tree weights will be returned. 148 | func Kruskal(dst graph.UndirectedBuilder, g UndirectedWeightLister) float64 { 149 | edges := g.Edges() 150 | ascend := make([]simple.Edge, 0, len(edges)) 151 | for _, e := range edges { 152 | u := e.From() 153 | v := e.To() 154 | w, ok := g.Weight(u, v) 155 | if !ok { 156 | panic("kruskal: unexpected invalid weight") 157 | } 158 | ascend = append(ascend, simple.Edge{F: u, T: v, W: w}) 159 | } 160 | sort.Sort(byWeight(ascend)) 161 | 162 | ds := newDisjointSet() 163 | for _, node := range g.Nodes() { 164 | ds.makeSet(node.ID()) 165 | } 166 | 167 | var w float64 168 | for _, e := range ascend { 169 | if s1, s2 := ds.find(e.From().ID()), ds.find(e.To().ID()); s1 != s2 { 170 | ds.union(s1, s2) 171 | dst.SetEdge(e) 172 | w += e.Weight() 173 | } 174 | } 175 | return w 176 | } 177 | 178 | type byWeight []simple.Edge 179 | 180 | func (e byWeight) Len() int { return len(e) } 181 | func (e byWeight) Less(i, j int) bool { return e[i].Weight() < e[j].Weight() } 182 | func (e byWeight) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 183 | -------------------------------------------------------------------------------- /path/weight.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package path 6 | 7 | import ( 8 | "math" 9 | 10 | "github.com/gonum/graph" 11 | ) 12 | 13 | // Weighting is a mapping between a pair of nodes and a weight. It follows the 14 | // semantics of the Weighter interface. 15 | type Weighting func(x, y graph.Node) (w float64, ok bool) 16 | 17 | // UniformCost returns a Weighting that returns an edge cost of 1 for existing 18 | // edges, zero for node identity and Inf for otherwise absent edges. 19 | func UniformCost(g graph.Graph) Weighting { 20 | return func(x, y graph.Node) (w float64, ok bool) { 21 | xid := x.ID() 22 | yid := y.ID() 23 | if xid == yid { 24 | return 0, true 25 | } 26 | if e := g.Edge(x, y); e != nil { 27 | return 1, true 28 | } 29 | return math.Inf(1), false 30 | } 31 | } 32 | 33 | // Heuristic returns an estimate of the cost of travelling between two nodes. 34 | type Heuristic func(x, y graph.Node) float64 35 | 36 | // HeuristicCoster wraps the HeuristicCost method. A graph implementing the 37 | // interface provides a heuristic between any two given nodes. 38 | type HeuristicCoster interface { 39 | HeuristicCost(x, y graph.Node) float64 40 | } 41 | -------------------------------------------------------------------------------- /simple/densegraph_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package simple 6 | 7 | import ( 8 | "math" 9 | "sort" 10 | "testing" 11 | 12 | "github.com/gonum/graph" 13 | "github.com/gonum/graph/internal/ordered" 14 | ) 15 | 16 | var ( 17 | _ graph.Graph = (*UndirectedMatrix)(nil) 18 | _ graph.Directed = (*DirectedMatrix)(nil) 19 | ) 20 | 21 | func TestBasicDenseImpassable(t *testing.T) { 22 | dg := NewUndirectedMatrix(5, math.Inf(1), 0, math.Inf(1)) 23 | if dg == nil { 24 | t.Fatal("Directed graph could not be made") 25 | } 26 | 27 | for i := 0; i < 5; i++ { 28 | if !dg.Has(Node(i)) { 29 | t.Errorf("Node that should exist doesn't: %d", i) 30 | } 31 | 32 | if degree := dg.Degree(Node(i)); degree != 0 { 33 | t.Errorf("Node in impassable graph has a neighbor. Node: %d Degree: %d", i, degree) 34 | } 35 | } 36 | 37 | for i := 5; i < 10; i++ { 38 | if dg.Has(Node(i)) { 39 | t.Errorf("Node exists that shouldn't: %d", i) 40 | } 41 | } 42 | } 43 | 44 | func TestBasicDensePassable(t *testing.T) { 45 | dg := NewUndirectedMatrix(5, 1, 0, math.Inf(1)) 46 | if dg == nil { 47 | t.Fatal("Directed graph could not be made") 48 | } 49 | 50 | for i := 0; i < 5; i++ { 51 | if !dg.Has(Node(i)) { 52 | t.Errorf("Node that should exist doesn't: %d", i) 53 | } 54 | 55 | if degree := dg.Degree(Node(i)); degree != 4 { 56 | t.Errorf("Node in passable graph missing neighbors. Node: %d Degree: %d", i, degree) 57 | } 58 | } 59 | 60 | for i := 5; i < 10; i++ { 61 | if dg.Has(Node(i)) { 62 | t.Errorf("Node exists that shouldn't: %d", i) 63 | } 64 | } 65 | } 66 | 67 | func TestDirectedDenseAddRemove(t *testing.T) { 68 | dg := NewDirectedMatrix(10, math.Inf(1), 0, math.Inf(1)) 69 | dg.SetEdge(Edge{F: Node(0), T: Node(2), W: 1}) 70 | 71 | if neighbors := dg.From(Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || 72 | dg.Edge(Node(0), Node(2)) == nil { 73 | t.Errorf("Adding edge didn't create successor") 74 | } 75 | 76 | dg.RemoveEdge(Edge{F: Node(0), T: Node(2)}) 77 | 78 | if neighbors := dg.From(Node(0)); len(neighbors) != 0 || dg.Edge(Node(0), Node(2)) != nil { 79 | t.Errorf("Removing edge didn't properly remove successor") 80 | } 81 | 82 | if neighbors := dg.To(Node(2)); len(neighbors) != 0 || dg.Edge(Node(0), Node(2)) != nil { 83 | t.Errorf("Removing directed edge wrongly kept predecessor") 84 | } 85 | 86 | dg.SetEdge(Edge{F: Node(0), T: Node(2), W: 2}) 87 | // I figure we've torture tested From/To at this point 88 | // so we'll just use the bool functions now 89 | if dg.Edge(Node(0), Node(2)) == nil { 90 | t.Fatal("Adding directed edge didn't change successor back") 91 | } 92 | c1, _ := dg.Weight(Node(2), Node(0)) 93 | c2, _ := dg.Weight(Node(0), Node(2)) 94 | if c1 == c2 { 95 | t.Error("Adding directed edge affected cost in undirected manner") 96 | } 97 | } 98 | 99 | func TestUndirectedDenseAddRemove(t *testing.T) { 100 | dg := NewUndirectedMatrix(10, math.Inf(1), 0, math.Inf(1)) 101 | dg.SetEdge(Edge{F: Node(0), T: Node(2)}) 102 | 103 | if neighbors := dg.From(Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || 104 | dg.EdgeBetween(Node(0), Node(2)) == nil { 105 | t.Errorf("Couldn't add neighbor") 106 | } 107 | 108 | if neighbors := dg.From(Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || 109 | dg.EdgeBetween(Node(2), Node(0)) == nil { 110 | t.Errorf("Adding an undirected neighbor didn't add it reciprocally") 111 | } 112 | } 113 | 114 | func TestDenseLists(t *testing.T) { 115 | dg := NewDirectedMatrix(15, 1, 0, math.Inf(1)) 116 | nodes := dg.Nodes() 117 | 118 | if len(nodes) != 15 { 119 | t.Fatalf("Wrong number of nodes") 120 | } 121 | 122 | sort.Sort(ordered.ByID(nodes)) 123 | 124 | for i, node := range dg.Nodes() { 125 | if i != node.ID() { 126 | t.Errorf("Node list doesn't return properly id'd nodes") 127 | } 128 | } 129 | 130 | edges := dg.Edges() 131 | if len(edges) != 15*14 { 132 | t.Errorf("Improper number of edges for passable dense graph") 133 | } 134 | 135 | dg.RemoveEdge(Edge{F: Node(12), T: Node(11)}) 136 | edges = dg.Edges() 137 | if len(edges) != (15*14)-1 { 138 | t.Errorf("Removing edge didn't affect edge listing properly") 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /simple/directed_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package simple 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | ) 13 | 14 | var _ graph.Graph = &DirectedGraph{} 15 | var _ graph.Directed = &DirectedGraph{} 16 | var _ graph.Directed = &DirectedGraph{} 17 | 18 | // Tests Issue #27 19 | func TestEdgeOvercounting(t *testing.T) { 20 | g := generateDummyGraph() 21 | 22 | if neigh := g.From(Node(Node(2))); len(neigh) != 2 { 23 | t.Errorf("Node 2 has incorrect number of neighbors got neighbors %v (count %d), expected 2 neighbors {0,1}", neigh, len(neigh)) 24 | } 25 | } 26 | 27 | func generateDummyGraph() *DirectedGraph { 28 | nodes := [4]struct{ srcID, targetID int }{ 29 | {2, 1}, 30 | {1, 0}, 31 | {2, 0}, 32 | {0, 2}, 33 | } 34 | 35 | g := NewDirectedGraph(0, math.Inf(1)) 36 | 37 | for _, n := range nodes { 38 | g.SetEdge(Edge{F: Node(n.srcID), T: Node(n.targetID), W: 1}) 39 | } 40 | 41 | return g 42 | } 43 | 44 | // Test for issue #123 https://github.com/gonum/graph/issues/123 45 | func TestIssue123DirectedGraph(t *testing.T) { 46 | defer func() { 47 | if r := recover(); r != nil { 48 | t.Errorf("unexpected panic: %v", r) 49 | } 50 | }() 51 | g := NewDirectedGraph(0, math.Inf(1)) 52 | 53 | n0 := Node(g.NewNodeID()) 54 | g.AddNode(n0) 55 | 56 | n1 := Node(g.NewNodeID()) 57 | g.AddNode(n1) 58 | 59 | g.RemoveNode(n0) 60 | 61 | n2 := Node(g.NewNodeID()) 62 | g.AddNode(n2) 63 | } 64 | -------------------------------------------------------------------------------- /simple/simple.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package simple provides a suite of simple graph implementations satisfying 9 | // the gonum/graph interfaces. 10 | package simple 11 | 12 | import ( 13 | "math" 14 | 15 | "github.com/gonum/graph" 16 | ) 17 | 18 | // Node is a simple graph node. 19 | type Node int 20 | 21 | // ID returns the ID number of the node. 22 | func (n Node) ID() int { 23 | return int(n) 24 | } 25 | 26 | // Edge is a simple graph edge. 27 | type Edge struct { 28 | F, T graph.Node 29 | W float64 30 | } 31 | 32 | // From returns the from-node of the edge. 33 | func (e Edge) From() graph.Node { return e.F } 34 | 35 | // To returns the to-node of the edge. 36 | func (e Edge) To() graph.Node { return e.T } 37 | 38 | // Weight returns the weight of the edge. 39 | func (e Edge) Weight() float64 { return e.W } 40 | 41 | // maxInt is the maximum value of the machine-dependent int type. 42 | const maxInt int = int(^uint(0) >> 1) 43 | 44 | // isSame returns whether two float64 values are the same where NaN values 45 | // are equalable. 46 | func isSame(a, b float64) bool { 47 | return a == b || (math.IsNaN(a) && math.IsNaN(b)) 48 | } 49 | -------------------------------------------------------------------------------- /simple/undirected_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package simple 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | ) 13 | 14 | var _ graph.Graph = (*UndirectedGraph)(nil) 15 | 16 | func TestAssertMutableNotDirected(t *testing.T) { 17 | var g graph.UndirectedBuilder = NewUndirectedGraph(0, math.Inf(1)) 18 | if _, ok := g.(graph.Directed); ok { 19 | t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!") 20 | } 21 | } 22 | 23 | func TestMaxID(t *testing.T) { 24 | g := NewUndirectedGraph(0, math.Inf(1)) 25 | nodes := make(map[graph.Node]struct{}) 26 | for i := Node(0); i < 3; i++ { 27 | g.AddNode(i) 28 | nodes[i] = struct{}{} 29 | } 30 | g.RemoveNode(Node(0)) 31 | delete(nodes, Node(0)) 32 | g.RemoveNode(Node(2)) 33 | delete(nodes, Node(2)) 34 | n := Node(g.NewNodeID()) 35 | g.AddNode(n) 36 | if !g.Has(n) { 37 | t.Error("added node does not exist in graph") 38 | } 39 | if _, exists := nodes[n]; exists { 40 | t.Errorf("Created already existing node id: %v", n.ID()) 41 | } 42 | } 43 | 44 | // Test for issue #123 https://github.com/gonum/graph/issues/123 45 | func TestIssue123UndirectedGraph(t *testing.T) { 46 | defer func() { 47 | if r := recover(); r != nil { 48 | t.Errorf("unexpected panic: %v", r) 49 | } 50 | }() 51 | g := NewUndirectedGraph(0, math.Inf(1)) 52 | 53 | n0 := Node(g.NewNodeID()) 54 | g.AddNode(n0) 55 | 56 | n1 := Node(g.NewNodeID()) 57 | g.AddNode(n1) 58 | 59 | g.RemoveNode(n0) 60 | 61 | n2 := Node(g.NewNodeID()) 62 | g.AddNode(n2) 63 | } 64 | -------------------------------------------------------------------------------- /topo/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package topo 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | "github.com/gonum/graph/graphs/gen" 13 | "github.com/gonum/graph/simple" 14 | ) 15 | 16 | var ( 17 | gnpDirected_10_tenth = gnpDirected(10, 0.1) 18 | gnpDirected_100_tenth = gnpDirected(100, 0.1) 19 | gnpDirected_1000_tenth = gnpDirected(1000, 0.1) 20 | gnpDirected_10_half = gnpDirected(10, 0.5) 21 | gnpDirected_100_half = gnpDirected(100, 0.5) 22 | gnpDirected_1000_half = gnpDirected(1000, 0.5) 23 | ) 24 | 25 | func gnpDirected(n int, p float64) graph.Directed { 26 | g := simple.NewDirectedGraph(0, math.Inf(1)) 27 | gen.Gnp(g, n, p, nil) 28 | return g 29 | } 30 | 31 | func benchmarkTarjanSCC(b *testing.B, g graph.Directed) { 32 | var sccs [][]graph.Node 33 | for i := 0; i < b.N; i++ { 34 | sccs = TarjanSCC(g) 35 | } 36 | if len(sccs) == 0 { 37 | b.Fatal("unexpected number zero-sized SCC set") 38 | } 39 | } 40 | 41 | func BenchmarkTarjanSCCGnp_10_tenth(b *testing.B) { 42 | benchmarkTarjanSCC(b, gnpDirected_10_tenth) 43 | } 44 | func BenchmarkTarjanSCCGnp_100_tenth(b *testing.B) { 45 | benchmarkTarjanSCC(b, gnpDirected_100_tenth) 46 | } 47 | func BenchmarkTarjanSCCGnp_1000_tenth(b *testing.B) { 48 | benchmarkTarjanSCC(b, gnpDirected_1000_tenth) 49 | } 50 | func BenchmarkTarjanSCCGnp_10_half(b *testing.B) { 51 | benchmarkTarjanSCC(b, gnpDirected_10_half) 52 | } 53 | func BenchmarkTarjanSCCGnp_100_half(b *testing.B) { 54 | benchmarkTarjanSCC(b, gnpDirected_100_half) 55 | } 56 | func BenchmarkTarjanSCCGnp_1000_half(b *testing.B) { 57 | benchmarkTarjanSCC(b, gnpDirected_1000_half) 58 | } 59 | -------------------------------------------------------------------------------- /topo/bron_kerbosch_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package topo 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/graph/internal/ordered" 14 | "github.com/gonum/graph/simple" 15 | ) 16 | 17 | var vOrderTests = []struct { 18 | g []intset 19 | wantCore [][]int 20 | wantK int 21 | }{ 22 | { 23 | g: []intset{ 24 | 0: linksTo(1, 2, 4, 6), 25 | 1: linksTo(2, 4, 6), 26 | 2: linksTo(3, 6), 27 | 3: linksTo(4, 5), 28 | 4: linksTo(6), 29 | 5: nil, 30 | 6: nil, 31 | }, 32 | wantCore: [][]int{ 33 | {}, 34 | {5}, 35 | {3}, 36 | {0, 1, 2, 4, 6}, 37 | }, 38 | wantK: 3, 39 | }, 40 | { 41 | g: batageljZaversnikGraph, 42 | wantCore: [][]int{ 43 | {0}, 44 | {5, 9, 10, 16}, 45 | {1, 2, 3, 4, 11, 12, 13, 15}, 46 | {6, 7, 8, 14, 17, 18, 19, 20}, 47 | }, 48 | wantK: 3, 49 | }, 50 | } 51 | 52 | func TestVertexOrdering(t *testing.T) { 53 | for i, test := range vOrderTests { 54 | g := simple.NewUndirectedGraph(0, math.Inf(1)) 55 | for u, e := range test.g { 56 | // Add nodes that are not defined by an edge. 57 | if !g.Has(simple.Node(u)) { 58 | g.AddNode(simple.Node(u)) 59 | } 60 | for v := range e { 61 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 62 | } 63 | } 64 | order, core := VertexOrdering(g) 65 | if len(core)-1 != test.wantK { 66 | t.Errorf("unexpected value of k for test %d: got: %d want: %d", i, len(core)-1, test.wantK) 67 | } 68 | var offset int 69 | for k, want := range test.wantCore { 70 | sort.Ints(want) 71 | got := make([]int, len(want)) 72 | for j, n := range order[len(order)-len(want)-offset : len(order)-offset] { 73 | got[j] = n.ID() 74 | } 75 | sort.Ints(got) 76 | if !reflect.DeepEqual(got, want) { 77 | t.Errorf("unexpected %d-core for test %d:\ngot: %v\nwant:%v", got, test.wantCore) 78 | } 79 | 80 | for j, n := range core[k] { 81 | got[j] = n.ID() 82 | } 83 | sort.Ints(got) 84 | if !reflect.DeepEqual(got, want) { 85 | t.Errorf("unexpected %d-core for test %d:\ngot: %v\nwant:%v", got, test.wantCore) 86 | } 87 | offset += len(want) 88 | } 89 | } 90 | } 91 | 92 | var bronKerboschTests = []struct { 93 | g []intset 94 | want [][]int 95 | }{ 96 | { 97 | // This is the example given in the Bron-Kerbosch article on wikipedia (renumbered). 98 | // http://en.wikipedia.org/w/index.php?title=Bron%E2%80%93Kerbosch_algorithm&oldid=656805858 99 | g: []intset{ 100 | 0: linksTo(1, 4), 101 | 1: linksTo(2, 4), 102 | 2: linksTo(3), 103 | 3: linksTo(4, 5), 104 | 4: nil, 105 | 5: nil, 106 | }, 107 | want: [][]int{ 108 | {0, 1, 4}, 109 | {1, 2}, 110 | {2, 3}, 111 | {3, 4}, 112 | {3, 5}, 113 | }, 114 | }, 115 | { 116 | g: batageljZaversnikGraph, 117 | want: [][]int{ 118 | {0}, 119 | {1, 2}, 120 | {1, 3}, 121 | {2, 4}, 122 | {3, 4}, 123 | {4, 5}, 124 | {6, 7, 8, 14}, 125 | {7, 11, 12}, 126 | {9, 11}, 127 | {10, 11}, 128 | {12, 18}, 129 | {13, 14, 15}, 130 | {14, 15, 17}, 131 | {15, 16}, 132 | {17, 18, 19, 20}, 133 | }, 134 | }, 135 | } 136 | 137 | func TestBronKerbosch(t *testing.T) { 138 | for i, test := range bronKerboschTests { 139 | g := simple.NewUndirectedGraph(0, math.Inf(1)) 140 | for u, e := range test.g { 141 | // Add nodes that are not defined by an edge. 142 | if !g.Has(simple.Node(u)) { 143 | g.AddNode(simple.Node(u)) 144 | } 145 | for v := range e { 146 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 147 | } 148 | } 149 | cliques := BronKerbosch(g) 150 | got := make([][]int, len(cliques)) 151 | for j, c := range cliques { 152 | ids := make([]int, len(c)) 153 | for k, n := range c { 154 | ids[k] = n.ID() 155 | } 156 | sort.Ints(ids) 157 | got[j] = ids 158 | } 159 | sort.Sort(ordered.BySliceValues(got)) 160 | if !reflect.DeepEqual(got, test.want) { 161 | t.Errorf("unexpected cliques for test %d:\ngot: %v\nwant:%v", i, got, test.want) 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /topo/common_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package topo 6 | 7 | // batageljZaversnikGraph is the example graph from 8 | // figure 1 of http://arxiv.org/abs/cs/0310049v1 9 | var batageljZaversnikGraph = []intset{ 10 | 0: nil, 11 | 12 | 1: linksTo(2, 3), 13 | 2: linksTo(4), 14 | 3: linksTo(4), 15 | 4: linksTo(5), 16 | 5: nil, 17 | 18 | 6: linksTo(7, 8, 14), 19 | 7: linksTo(8, 11, 12, 14), 20 | 8: linksTo(14), 21 | 9: linksTo(11), 22 | 10: linksTo(11), 23 | 11: linksTo(12), 24 | 12: linksTo(18), 25 | 13: linksTo(14, 15), 26 | 14: linksTo(15, 17), 27 | 15: linksTo(16, 17), 28 | 16: nil, 29 | 17: linksTo(18, 19, 20), 30 | 18: linksTo(19, 20), 31 | 19: linksTo(20), 32 | 20: nil, 33 | } 34 | 35 | // intset is an integer set. 36 | type intset map[int]struct{} 37 | 38 | func linksTo(i ...int) intset { 39 | if len(i) == 0 { 40 | return nil 41 | } 42 | s := make(intset) 43 | for _, v := range i { 44 | s[v] = struct{}{} 45 | } 46 | return s 47 | } 48 | -------------------------------------------------------------------------------- /topo/johnson_cycles_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package topo 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/graph/internal/ordered" 14 | "github.com/gonum/graph/simple" 15 | ) 16 | 17 | var cyclesInTests = []struct { 18 | g []intset 19 | sccs [][]int 20 | want [][]int 21 | }{ 22 | { 23 | g: []intset{ 24 | 0: linksTo(1), 25 | 1: linksTo(2, 7), 26 | 2: linksTo(3, 6), 27 | 3: linksTo(4), 28 | 4: linksTo(2, 5), 29 | 6: linksTo(3, 5), 30 | 7: linksTo(0, 6), 31 | }, 32 | want: [][]int{ 33 | {0, 1, 7, 0}, 34 | {2, 3, 4, 2}, 35 | {2, 6, 3, 4, 2}, 36 | }, 37 | }, 38 | { 39 | g: []intset{ 40 | 0: linksTo(1, 2, 3), 41 | 1: linksTo(2), 42 | 2: linksTo(3), 43 | 3: linksTo(1), 44 | }, 45 | want: [][]int{ 46 | {1, 2, 3, 1}, 47 | }, 48 | }, 49 | { 50 | g: []intset{ 51 | 0: linksTo(1), 52 | 1: linksTo(0, 2), 53 | 2: linksTo(1), 54 | }, 55 | want: [][]int{ 56 | {0, 1, 0}, 57 | {1, 2, 1}, 58 | }, 59 | }, 60 | { 61 | g: []intset{ 62 | 0: linksTo(1), 63 | 1: linksTo(2, 3), 64 | 2: linksTo(4, 5), 65 | 3: linksTo(4, 5), 66 | 4: linksTo(6), 67 | 5: nil, 68 | 6: nil, 69 | }, 70 | want: nil, 71 | }, 72 | { 73 | g: []intset{ 74 | 0: linksTo(1), 75 | 1: linksTo(2, 3, 4), 76 | 2: linksTo(0, 3), 77 | 3: linksTo(4), 78 | 4: linksTo(3), 79 | }, 80 | want: [][]int{ 81 | {0, 1, 2, 0}, 82 | {3, 4, 3}, 83 | }, 84 | }, 85 | } 86 | 87 | func TestCyclesIn(t *testing.T) { 88 | for i, test := range cyclesInTests { 89 | g := simple.NewDirectedGraph(0, math.Inf(1)) 90 | g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs. 91 | for u, e := range test.g { 92 | // Add nodes that are not defined by an edge. 93 | if !g.Has(simple.Node(u)) { 94 | g.AddNode(simple.Node(u)) 95 | } 96 | for v := range e { 97 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 98 | } 99 | } 100 | cycles := CyclesIn(g) 101 | var got [][]int 102 | if cycles != nil { 103 | got = make([][]int, len(cycles)) 104 | } 105 | // johnson.circuit does range iteration over maps, 106 | // so sort to ensure consistent ordering. 107 | for j, c := range cycles { 108 | ids := make([]int, len(c)) 109 | for k, n := range c { 110 | ids[k] = n.ID() 111 | } 112 | got[j] = ids 113 | } 114 | sort.Sort(ordered.BySliceValues(got)) 115 | if !reflect.DeepEqual(got, test.want) { 116 | t.Errorf("unexpected johnson result for %d:\n\tgot:%#v\n\twant:%#v", i, got, test.want) 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /topo/non_tomita_choice.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //+build !tomita 6 | 7 | package topo 8 | 9 | const tomitaTanakaTakahashi = false 10 | -------------------------------------------------------------------------------- /topo/tomita_choice.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //+build tomita 6 | 7 | package topo 8 | 9 | const tomitaTanakaTakahashi = true 10 | -------------------------------------------------------------------------------- /topo/topo.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package topo provides graph topology analysis functions. 9 | package topo 10 | 11 | import ( 12 | "github.com/gonum/graph" 13 | "github.com/gonum/graph/traverse" 14 | ) 15 | 16 | // IsPathIn returns whether path is a path in g. 17 | // 18 | // As special cases, IsPathIn returns true for a zero length path or for 19 | // a path of length 1 when the node in path exists in the graph. 20 | func IsPathIn(g graph.Graph, path []graph.Node) bool { 21 | switch len(path) { 22 | case 0: 23 | return true 24 | case 1: 25 | return g.Has(path[0]) 26 | default: 27 | var canReach func(u, v graph.Node) bool 28 | switch g := g.(type) { 29 | case graph.Directed: 30 | canReach = g.HasEdgeFromTo 31 | default: 32 | canReach = g.HasEdgeBetween 33 | } 34 | 35 | for i, u := range path[:len(path)-1] { 36 | if !canReach(u, path[i+1]) { 37 | return false 38 | } 39 | } 40 | return true 41 | } 42 | } 43 | 44 | // PathExistsIn returns whether there is a path in g starting at from extending 45 | // to to. 46 | // 47 | // PathExistsIn exists as a helper function. If many tests for path existence 48 | // are being performed, other approaches will be more efficient. 49 | func PathExistsIn(g graph.Graph, from, to graph.Node) bool { 50 | var t traverse.BreadthFirst 51 | return t.Walk(g, from, func(n graph.Node, _ int) bool { return n.ID() == to.ID() }) != nil 52 | } 53 | 54 | // ConnectedComponents returns the connected components of the undirected graph g. 55 | func ConnectedComponents(g graph.Undirected) [][]graph.Node { 56 | var ( 57 | w traverse.DepthFirst 58 | c []graph.Node 59 | cc [][]graph.Node 60 | ) 61 | during := func(n graph.Node) { 62 | c = append(c, n) 63 | } 64 | after := func() { 65 | cc = append(cc, []graph.Node(nil)) 66 | cc[len(cc)-1] = append(cc[len(cc)-1], c...) 67 | c = c[:0] 68 | } 69 | w.WalkAll(g, nil, after, during) 70 | 71 | return cc 72 | } 73 | -------------------------------------------------------------------------------- /topo/topo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package topo 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "sort" 11 | "testing" 12 | 13 | "github.com/gonum/graph" 14 | "github.com/gonum/graph/internal/ordered" 15 | "github.com/gonum/graph/simple" 16 | ) 17 | 18 | func TestIsPath(t *testing.T) { 19 | dg := simple.NewDirectedGraph(0, math.Inf(1)) 20 | if !IsPathIn(dg, nil) { 21 | t.Error("IsPath returns false on nil path") 22 | } 23 | p := []graph.Node{simple.Node(0)} 24 | if IsPathIn(dg, p) { 25 | t.Error("IsPath returns true on nonexistant node") 26 | } 27 | dg.AddNode(p[0]) 28 | if !IsPathIn(dg, p) { 29 | t.Error("IsPath returns false on single-length path with existing node") 30 | } 31 | p = append(p, simple.Node(1)) 32 | dg.AddNode(p[1]) 33 | if IsPathIn(dg, p) { 34 | t.Error("IsPath returns true on bad path of length 2") 35 | } 36 | dg.SetEdge(simple.Edge{F: p[0], T: p[1], W: 1}) 37 | if !IsPathIn(dg, p) { 38 | t.Error("IsPath returns false on correct path of length 2") 39 | } 40 | p[0], p[1] = p[1], p[0] 41 | if IsPathIn(dg, p) { 42 | t.Error("IsPath erroneously returns true for a reverse path") 43 | } 44 | p = []graph.Node{p[1], p[0], simple.Node(2)} 45 | dg.SetEdge(simple.Edge{F: p[1], T: p[2], W: 1}) 46 | if !IsPathIn(dg, p) { 47 | t.Error("IsPath does not find a correct path for path > 2 nodes") 48 | } 49 | ug := simple.NewUndirectedGraph(0, math.Inf(1)) 50 | ug.SetEdge(simple.Edge{F: p[1], T: p[0], W: 1}) 51 | ug.SetEdge(simple.Edge{F: p[1], T: p[2], W: 1}) 52 | if !IsPathIn(dg, p) { 53 | t.Error("IsPath does not correctly account for undirected behavior") 54 | } 55 | } 56 | 57 | var pathExistsInUndirectedTests = []struct { 58 | g []intset 59 | from, to int 60 | want bool 61 | }{ 62 | {g: batageljZaversnikGraph, from: 0, to: 0, want: true}, 63 | {g: batageljZaversnikGraph, from: 0, to: 1, want: false}, 64 | {g: batageljZaversnikGraph, from: 1, to: 2, want: true}, 65 | {g: batageljZaversnikGraph, from: 2, to: 1, want: true}, 66 | {g: batageljZaversnikGraph, from: 2, to: 12, want: false}, 67 | {g: batageljZaversnikGraph, from: 20, to: 6, want: true}, 68 | } 69 | 70 | func TestPathExistsInUndirected(t *testing.T) { 71 | for i, test := range pathExistsInUndirectedTests { 72 | g := simple.NewUndirectedGraph(0, math.Inf(1)) 73 | 74 | for u, e := range test.g { 75 | if !g.Has(simple.Node(u)) { 76 | g.AddNode(simple.Node(u)) 77 | } 78 | for v := range e { 79 | if !g.Has(simple.Node(v)) { 80 | g.AddNode(simple.Node(v)) 81 | } 82 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 83 | } 84 | } 85 | 86 | got := PathExistsIn(g, simple.Node(test.from), simple.Node(test.to)) 87 | if got != test.want { 88 | t.Errorf("unexpected result for path existance in test %d: got:%t want %t", i, got, test.want) 89 | } 90 | } 91 | } 92 | 93 | var pathExistsInDirectedTests = []struct { 94 | g []intset 95 | from, to int 96 | want bool 97 | }{ 98 | // The graph definition is such that from node IDs are 99 | // less than to node IDs. 100 | {g: batageljZaversnikGraph, from: 0, to: 0, want: true}, 101 | {g: batageljZaversnikGraph, from: 0, to: 1, want: false}, 102 | {g: batageljZaversnikGraph, from: 1, to: 2, want: true}, 103 | {g: batageljZaversnikGraph, from: 2, to: 1, want: false}, 104 | {g: batageljZaversnikGraph, from: 2, to: 12, want: false}, 105 | {g: batageljZaversnikGraph, from: 20, to: 6, want: false}, 106 | {g: batageljZaversnikGraph, from: 6, to: 20, want: true}, 107 | } 108 | 109 | func TestPathExistsInDirected(t *testing.T) { 110 | for i, test := range pathExistsInDirectedTests { 111 | g := simple.NewDirectedGraph(0, math.Inf(1)) 112 | 113 | for u, e := range test.g { 114 | if !g.Has(simple.Node(u)) { 115 | g.AddNode(simple.Node(u)) 116 | } 117 | for v := range e { 118 | if !g.Has(simple.Node(v)) { 119 | g.AddNode(simple.Node(v)) 120 | } 121 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 122 | } 123 | } 124 | 125 | got := PathExistsIn(g, simple.Node(test.from), simple.Node(test.to)) 126 | if got != test.want { 127 | t.Errorf("unexpected result for path existance in test %d: got:%t want %t", i, got, test.want) 128 | } 129 | } 130 | } 131 | 132 | var connectedComponentTests = []struct { 133 | g []intset 134 | want [][]int 135 | }{ 136 | { 137 | g: batageljZaversnikGraph, 138 | want: [][]int{ 139 | {0}, 140 | {1, 2, 3, 4, 5}, 141 | {6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 142 | }, 143 | }, 144 | } 145 | 146 | func TestConnectedComponents(t *testing.T) { 147 | for i, test := range connectedComponentTests { 148 | g := simple.NewUndirectedGraph(0, math.Inf(1)) 149 | 150 | for u, e := range test.g { 151 | if !g.Has(simple.Node(u)) { 152 | g.AddNode(simple.Node(u)) 153 | } 154 | for v := range e { 155 | if !g.Has(simple.Node(v)) { 156 | g.AddNode(simple.Node(v)) 157 | } 158 | g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 159 | } 160 | } 161 | cc := ConnectedComponents(g) 162 | got := make([][]int, len(cc)) 163 | for j, c := range cc { 164 | ids := make([]int, len(c)) 165 | for k, n := range c { 166 | ids[k] = n.ID() 167 | } 168 | sort.Ints(ids) 169 | got[j] = ids 170 | } 171 | sort.Sort(ordered.BySliceValues(got)) 172 | if !reflect.DeepEqual(got, test.want) { 173 | t.Errorf("unexpected connected components for test %d %T:\ngot: %v\nwant:%v", i, g, got, test.want) 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /traverse/traverse.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package traverse provides basic graph traversal primitives. 9 | package traverse 10 | 11 | import ( 12 | "golang.org/x/tools/container/intsets" 13 | 14 | "github.com/gonum/graph" 15 | "github.com/gonum/graph/internal/linear" 16 | ) 17 | 18 | // BreadthFirst implements stateful breadth-first graph traversal. 19 | type BreadthFirst struct { 20 | EdgeFilter func(graph.Edge) bool 21 | Visit func(u, v graph.Node) 22 | queue linear.NodeQueue 23 | visited *intsets.Sparse 24 | } 25 | 26 | // Walk performs a breadth-first traversal of the graph g starting from the given node, 27 | // depending on the the EdgeFilter field and the until parameter if they are non-nil. The 28 | // traversal follows edges for which EdgeFilter(edge) is true and returns the first node 29 | // for which until(node, depth) is true. During the traversal, if the Visit field is 30 | // non-nil, it is called with the nodes joined by each followed edge. 31 | func (b *BreadthFirst) Walk(g graph.Graph, from graph.Node, until func(n graph.Node, d int) bool) graph.Node { 32 | if b.visited == nil { 33 | b.visited = &intsets.Sparse{} 34 | } 35 | b.queue.Enqueue(from) 36 | b.visited.Insert(from.ID()) 37 | 38 | var ( 39 | depth int 40 | children int 41 | untilNext = 1 42 | ) 43 | for b.queue.Len() > 0 { 44 | t := b.queue.Dequeue() 45 | if until != nil && until(t, depth) { 46 | return t 47 | } 48 | for _, n := range g.From(t) { 49 | if b.EdgeFilter != nil && !b.EdgeFilter(g.Edge(t, n)) { 50 | continue 51 | } 52 | if b.visited.Has(n.ID()) { 53 | continue 54 | } 55 | if b.Visit != nil { 56 | b.Visit(t, n) 57 | } 58 | b.visited.Insert(n.ID()) 59 | children++ 60 | b.queue.Enqueue(n) 61 | } 62 | if untilNext--; untilNext == 0 { 63 | depth++ 64 | untilNext = children 65 | children = 0 66 | } 67 | } 68 | 69 | return nil 70 | } 71 | 72 | // WalkAll calls Walk for each unvisited node of the graph g using edges independent 73 | // of their direction. The functions before and after are called prior to commencing 74 | // and after completing each walk if they are non-nil respectively. The function 75 | // during is called on each node as it is traversed. 76 | func (b *BreadthFirst) WalkAll(g graph.Undirected, before, after func(), during func(graph.Node)) { 77 | b.Reset() 78 | for _, from := range g.Nodes() { 79 | if b.Visited(from) { 80 | continue 81 | } 82 | if before != nil { 83 | before() 84 | } 85 | b.Walk(g, from, func(n graph.Node, _ int) bool { 86 | if during != nil { 87 | during(n) 88 | } 89 | return false 90 | }) 91 | if after != nil { 92 | after() 93 | } 94 | } 95 | } 96 | 97 | // Visited returned whether the node n was visited during a traverse. 98 | func (b *BreadthFirst) Visited(n graph.Node) bool { 99 | return b.visited != nil && b.visited.Has(n.ID()) 100 | } 101 | 102 | // Reset resets the state of the traverser for reuse. 103 | func (b *BreadthFirst) Reset() { 104 | b.queue.Reset() 105 | if b.visited != nil { 106 | b.visited.Clear() 107 | } 108 | } 109 | 110 | // DepthFirst implements stateful depth-first graph traversal. 111 | type DepthFirst struct { 112 | EdgeFilter func(graph.Edge) bool 113 | Visit func(u, v graph.Node) 114 | stack linear.NodeStack 115 | visited *intsets.Sparse 116 | } 117 | 118 | // Walk performs a depth-first traversal of the graph g starting from the given node, 119 | // depending on the the EdgeFilter field and the until parameter if they are non-nil. The 120 | // traversal follows edges for which EdgeFilter(edge) is true and returns the first node 121 | // for which until(node) is true. During the traversal, if the Visit field is non-nil, it 122 | // is called with the nodes joined by each followed edge. 123 | func (d *DepthFirst) Walk(g graph.Graph, from graph.Node, until func(graph.Node) bool) graph.Node { 124 | if d.visited == nil { 125 | d.visited = &intsets.Sparse{} 126 | } 127 | d.stack.Push(from) 128 | d.visited.Insert(from.ID()) 129 | 130 | for d.stack.Len() > 0 { 131 | t := d.stack.Pop() 132 | if until != nil && until(t) { 133 | return t 134 | } 135 | for _, n := range g.From(t) { 136 | if d.EdgeFilter != nil && !d.EdgeFilter(g.Edge(t, n)) { 137 | continue 138 | } 139 | if d.visited.Has(n.ID()) { 140 | continue 141 | } 142 | if d.Visit != nil { 143 | d.Visit(t, n) 144 | } 145 | d.visited.Insert(n.ID()) 146 | d.stack.Push(n) 147 | } 148 | } 149 | 150 | return nil 151 | } 152 | 153 | // WalkAll calls Walk for each unvisited node of the graph g using edges independent 154 | // of their direction. The functions before and after are called prior to commencing 155 | // and after completing each walk if they are non-nil respectively. The function 156 | // during is called on each node as it is traversed. 157 | func (d *DepthFirst) WalkAll(g graph.Undirected, before, after func(), during func(graph.Node)) { 158 | d.Reset() 159 | for _, from := range g.Nodes() { 160 | if d.Visited(from) { 161 | continue 162 | } 163 | if before != nil { 164 | before() 165 | } 166 | d.Walk(g, from, func(n graph.Node) bool { 167 | if during != nil { 168 | during(n) 169 | } 170 | return false 171 | }) 172 | if after != nil { 173 | after() 174 | } 175 | } 176 | } 177 | 178 | // Visited returned whether the node n was visited during a traverse. 179 | func (d *DepthFirst) Visited(n graph.Node) bool { 180 | return d.visited != nil && d.visited.Has(n.ID()) 181 | } 182 | 183 | // Reset resets the state of the traverser for reuse. 184 | func (d *DepthFirst) Reset() { 185 | d.stack = d.stack[:0] 186 | if d.visited != nil { 187 | d.visited.Clear() 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /undirect.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package graph 6 | 7 | import ( 8 | "golang.org/x/tools/container/intsets" 9 | ) 10 | 11 | // Undirect converts a directed graph to an undirected graph, resolving 12 | // edge weight conflicts. 13 | type Undirect struct { 14 | G Directed 15 | 16 | // Absent is the value used to 17 | // represent absent edge weights 18 | // passed to Merge if the reverse 19 | // edge is present. 20 | Absent float64 21 | 22 | // Merge defines how discordant edge 23 | // weights in G are resolved. A merge 24 | // is performed if at least one edge 25 | // exists between the nodes being 26 | // considered. The edges corresponding 27 | // to the two weights are also passed, 28 | // in the same order. 29 | // The order of weight parameters 30 | // passed to Merge is not defined, so 31 | // the function should be commutative. 32 | // If Merge is nil, the arithmetic 33 | // mean is used to merge weights. 34 | Merge func(x, y float64, xe, ye Edge) float64 35 | } 36 | 37 | var ( 38 | _ Undirected = Undirect{} 39 | _ Weighter = Undirect{} 40 | ) 41 | 42 | // Has returns whether the node exists within the graph. 43 | func (g Undirect) Has(n Node) bool { return g.G.Has(n) } 44 | 45 | // Nodes returns all the nodes in the graph. 46 | func (g Undirect) Nodes() []Node { return g.G.Nodes() } 47 | 48 | // From returns all nodes in g that can be reached directly from u. 49 | func (g Undirect) From(u Node) []Node { 50 | var ( 51 | nodes []Node 52 | seen intsets.Sparse 53 | ) 54 | for _, n := range g.G.From(u) { 55 | seen.Insert(n.ID()) 56 | nodes = append(nodes, n) 57 | } 58 | for _, n := range g.G.To(u) { 59 | id := n.ID() 60 | if seen.Has(id) { 61 | continue 62 | } 63 | seen.Insert(id) 64 | nodes = append(nodes, n) 65 | } 66 | return nodes 67 | } 68 | 69 | // HasEdgeBetween returns whether an edge exists between nodes x and y. 70 | func (g Undirect) HasEdgeBetween(x, y Node) bool { return g.G.HasEdgeBetween(x, y) } 71 | 72 | // Edge returns the edge from u to v if such an edge exists and nil otherwise. 73 | // The node v must be directly reachable from u as defined by the From method. 74 | // If an edge exists, the Edge returned is an EdgePair. The weight of 75 | // the edge is determined by applying the Merge func to the weights of the 76 | // edges between u and v. 77 | func (g Undirect) Edge(u, v Node) Edge { return g.EdgeBetween(u, v) } 78 | 79 | // EdgeBetween returns the edge between nodes x and y. If an edge exists, the 80 | // Edge returned is an EdgePair. The weight of the edge is determined by 81 | // applying the Merge func to the weights of edges between x and y. 82 | func (g Undirect) EdgeBetween(x, y Node) Edge { 83 | fe := g.G.Edge(x, y) 84 | re := g.G.Edge(y, x) 85 | if fe == nil && re == nil { 86 | return nil 87 | } 88 | 89 | var f, r float64 90 | if wg, ok := g.G.(Weighter); ok { 91 | f, ok = wg.Weight(x, y) 92 | if !ok { 93 | f = g.Absent 94 | } 95 | r, ok = wg.Weight(y, x) 96 | if !ok { 97 | r = g.Absent 98 | } 99 | } else { 100 | f = g.Absent 101 | if fe != nil { 102 | f = fe.Weight() 103 | } 104 | r = g.Absent 105 | if re != nil { 106 | r = re.Weight() 107 | } 108 | } 109 | 110 | var w float64 111 | if g.Merge == nil { 112 | w = (f + r) / 2 113 | } else { 114 | w = g.Merge(f, r, fe, re) 115 | } 116 | return EdgePair{E: [2]Edge{fe, re}, W: w} 117 | } 118 | 119 | // Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge. 120 | // If x and y are the same node the internal node weight is returned. If there is no joining 121 | // edge between the two nodes the weight value returned is zero. Weight returns true if an edge 122 | // exists between x and y or if x and y have the same ID, false otherwise. 123 | func (g Undirect) Weight(x, y Node) (w float64, ok bool) { 124 | fe := g.G.Edge(x, y) 125 | re := g.G.Edge(y, x) 126 | 127 | var f, r float64 128 | if wg, wOk := g.G.(Weighter); wOk { 129 | var fOk, rOK bool 130 | f, fOk = wg.Weight(x, y) 131 | if !fOk { 132 | f = g.Absent 133 | } 134 | r, rOK = wg.Weight(y, x) 135 | if !rOK { 136 | r = g.Absent 137 | } 138 | ok = fOk || rOK 139 | } else { 140 | f = g.Absent 141 | if fe != nil { 142 | f = fe.Weight() 143 | ok = true 144 | } 145 | r = g.Absent 146 | if re != nil { 147 | r = re.Weight() 148 | ok = true 149 | } 150 | } 151 | 152 | if g.Merge == nil { 153 | return (f + r) / 2, ok 154 | } 155 | return g.Merge(f, r, fe, re), ok 156 | } 157 | 158 | // EdgePair is an opposed pair of directed edges. 159 | type EdgePair struct { 160 | E [2]Edge 161 | W float64 162 | } 163 | 164 | // From returns the from node of the first non-nil edge, or nil. 165 | func (e EdgePair) From() Node { 166 | if e.E[0] != nil { 167 | return e.E[0].From() 168 | } else if e.E[1] != nil { 169 | return e.E[1].From() 170 | } 171 | return nil 172 | } 173 | 174 | // To returns the to node of the first non-nil edge, or nil. 175 | func (e EdgePair) To() Node { 176 | if e.E[0] != nil { 177 | return e.E[0].To() 178 | } else if e.E[1] != nil { 179 | return e.E[1].To() 180 | } 181 | return nil 182 | } 183 | 184 | // Weight returns the merged edge weights of the two edges. 185 | func (e EdgePair) Weight() float64 { return e.W } 186 | -------------------------------------------------------------------------------- /undirect_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2015 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package graph_test 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | 11 | "github.com/gonum/graph" 12 | "github.com/gonum/graph/simple" 13 | "github.com/gonum/matrix/mat64" 14 | ) 15 | 16 | var directedGraphs = []struct { 17 | g func() graph.DirectedBuilder 18 | edges []simple.Edge 19 | absent float64 20 | merge func(x, y float64, xe, ye graph.Edge) float64 21 | 22 | want mat64.Matrix 23 | }{ 24 | { 25 | g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, 26 | edges: []simple.Edge{ 27 | {F: simple.Node(0), T: simple.Node(1), W: 2}, 28 | {F: simple.Node(1), T: simple.Node(0), W: 1}, 29 | {F: simple.Node(1), T: simple.Node(2), W: 1}, 30 | }, 31 | want: mat64.NewSymDense(3, []float64{ 32 | 0, (1. + 2.) / 2., 0, 33 | (1. + 2.) / 2., 0, 1. / 2., 34 | 0, 1. / 2., 0, 35 | }), 36 | }, 37 | { 38 | g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, 39 | edges: []simple.Edge{ 40 | {F: simple.Node(0), T: simple.Node(1), W: 2}, 41 | {F: simple.Node(1), T: simple.Node(0), W: 1}, 42 | {F: simple.Node(1), T: simple.Node(2), W: 1}, 43 | }, 44 | absent: 1, 45 | merge: func(x, y float64, _, _ graph.Edge) float64 { return math.Sqrt(x * y) }, 46 | want: mat64.NewSymDense(3, []float64{ 47 | 0, math.Sqrt(1 * 2), 0, 48 | math.Sqrt(1 * 2), 0, math.Sqrt(1 * 1), 49 | 0, math.Sqrt(1 * 1), 0, 50 | }), 51 | }, 52 | { 53 | g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, 54 | edges: []simple.Edge{ 55 | {F: simple.Node(0), T: simple.Node(1), W: 2}, 56 | {F: simple.Node(1), T: simple.Node(0), W: 1}, 57 | {F: simple.Node(1), T: simple.Node(2), W: 1}, 58 | }, 59 | merge: func(x, y float64, _, _ graph.Edge) float64 { return math.Min(x, y) }, 60 | want: mat64.NewSymDense(3, []float64{ 61 | 0, math.Min(1, 2), 0, 62 | math.Min(1, 2), 0, math.Min(1, 0), 63 | 0, math.Min(1, 0), 0, 64 | }), 65 | }, 66 | { 67 | g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, 68 | edges: []simple.Edge{ 69 | {F: simple.Node(0), T: simple.Node(1), W: 2}, 70 | {F: simple.Node(1), T: simple.Node(0), W: 1}, 71 | {F: simple.Node(1), T: simple.Node(2), W: 1}, 72 | }, 73 | merge: func(x, y float64, xe, ye graph.Edge) float64 { 74 | if xe == nil { 75 | return y 76 | } 77 | if ye == nil { 78 | return x 79 | } 80 | return math.Min(x, y) 81 | }, 82 | want: mat64.NewSymDense(3, []float64{ 83 | 0, math.Min(1, 2), 0, 84 | math.Min(1, 2), 0, 1, 85 | 0, 1, 0, 86 | }), 87 | }, 88 | { 89 | g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, 90 | edges: []simple.Edge{ 91 | {F: simple.Node(0), T: simple.Node(1), W: 2}, 92 | {F: simple.Node(1), T: simple.Node(0), W: 1}, 93 | {F: simple.Node(1), T: simple.Node(2), W: 1}, 94 | }, 95 | merge: func(x, y float64, _, _ graph.Edge) float64 { return math.Max(x, y) }, 96 | want: mat64.NewSymDense(3, []float64{ 97 | 0, math.Max(1, 2), 0, 98 | math.Max(1, 2), 0, math.Max(1, 0), 99 | 0, math.Max(1, 0), 0, 100 | }), 101 | }, 102 | } 103 | 104 | func TestUndirect(t *testing.T) { 105 | for _, test := range directedGraphs { 106 | g := test.g() 107 | for _, e := range test.edges { 108 | g.SetEdge(e) 109 | } 110 | 111 | src := graph.Undirect{G: g, Absent: test.absent, Merge: test.merge} 112 | dst := simple.NewUndirectedMatrixFrom(src.Nodes(), 0, 0, 0) 113 | for _, u := range src.Nodes() { 114 | for _, v := range src.From(u) { 115 | dst.SetEdge(src.Edge(u, v)) 116 | } 117 | } 118 | 119 | if !mat64.Equal(dst.Matrix(), test.want) { 120 | t.Errorf("unexpected result:\ngot:\n%.4v\nwant:\n%.4v", 121 | mat64.Formatted(dst.Matrix()), 122 | mat64.Formatted(test.want), 123 | ) 124 | } 125 | } 126 | } 127 | --------------------------------------------------------------------------------