├── .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 [](https://travis-ci.org/gonum/graph) [](https://coveralls.io/github/gonum/graph?branch=master) [](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 |
--------------------------------------------------------------------------------