diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index c05a464507c5..3930802f7488 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -651,6 +651,11 @@ "ImportPath": "github.com/golang/protobuf/proto", "Rev": "7f07925444bb51fa4cf9dfe6f7661876f8852275" }, + { + "ImportPath": "github.com/gonum/graph", + "Comment": "v0.1-11-gf6ac2b0", + "Rev": "f6ac2b0f80f5a28ee70af78ce415393b37bcd6c1" + }, { "ImportPath": "github.com/google/cadvisor/api", "Comment": "0.10.1-106-gfd9f7e0", diff --git a/Godeps/_workspace/src/github.com/GoogleCloudPlatform/kubernetes/pkg/client/record/event.go b/Godeps/_workspace/src/github.com/GoogleCloudPlatform/kubernetes/pkg/client/record/event.go index b41af7704e23..f4c58ff44e76 100644 --- a/Godeps/_workspace/src/github.com/GoogleCloudPlatform/kubernetes/pkg/client/record/event.go +++ b/Godeps/_workspace/src/github.com/GoogleCloudPlatform/kubernetes/pkg/client/record/event.go @@ -110,6 +110,7 @@ func recordEvent(sink EventSink, event *api.Event, updateExistingEvent bool) boo // If we can't contact the server, then hold everything while we keep trying. // Otherwise, something about the event is malformed and we should abandon it. giveUp := false + alreadyExists := errors.IsAlreadyExists(err) switch err.(type) { case *client.RequestConstructionError: // We will construct the request the same next time, so don't keep trying. @@ -124,7 +125,9 @@ func recordEvent(sink EventSink, event *api.Event, updateExistingEvent bool) boo // This case includes actual http transport errors. Go ahead and retry. } if giveUp { - glog.Errorf("Unable to write event '%#v': '%v' (will not retry!)", event, err) + if !alreadyExists { + glog.Errorf("Unable to write event '%#v': '%v' (will not retry!)", event, err) + } return true } glog.Errorf("Unable to write event: '%v' (may retry after sleeping)", err) diff --git a/Godeps/_workspace/src/github.com/gonum/graph/.gitignore b/Godeps/_workspace/src/github.com/gonum/graph/.gitignore new file mode 100644 index 000000000000..86e0d2404455 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/.gitignore @@ -0,0 +1 @@ +test.out \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/gonum/graph/.travis.yml b/Godeps/_workspace/src/github.com/gonum/graph/.travis.yml new file mode 100644 index 000000000000..0aa5032185b9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/.travis.yml @@ -0,0 +1,34 @@ +sudo: false + +language: go + +go: + - 1.3.3 + - 1.4.2 + - tip + +env: + global: + - secure: "DFMafUo/eJVHYw6k2pHzu/zVTE0d0Aeec+YeaFnw2QlP+mr7jIpj7hYwBMeYm7ZromVRvF6cAW4R1za6NeugzHEEIll5cnWwLyGXWMq25hd7xOd+vq9qzY00OEKecHQgXpDx6RZkMvQEkHcuiWxueCe0BH41gtA8iVQ9TXjNbgQ=" + +before_install: + - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi + - go get github.com/mattn/goveralls + +script: + - go get -d -t -v ./... + - go build -x -v ./... + - go test -x -v ./... + - diff <(gofmt -d .) <("") + - if [[ $TRAVIS_SECURE_ENV_VARS = "true" ]]; then bash test-coverage.sh; fi + +after_failure: failure + +notifications: + email: + recipients: + - jonathan.lawlor@gmail.com + - jragonmiris@gmail.com + on_success: change + on_failure: always + diff --git a/Godeps/_workspace/src/github.com/gonum/graph/README.md b/Godeps/_workspace/src/github.com/gonum/graph/README.md new file mode 100644 index 000000000000..3c4c17962815 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/README.md @@ -0,0 +1,15 @@ +# Gonum Graph [![Build Status](https://travis-ci.org/gonum/graph.svg?branch=master)](https://travis-ci.org/gonum/graph) [![Coverage Status](https://img.shields.io/coveralls/gonum/graph.svg)](https://coveralls.io/r/gonum/graph?branch=master) + +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. + +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. + +## Issues + +If you find any bugs, feel free to file an issue on the github issue tracker. Discussions on API changes, added features, code review, or similar requests are preferred on the Gonum-dev Google Group. + +https://groups.google.com/forum/#!forum/gonum-dev + +## License + +Please see github.com/gonum/license for general license information, contributors, authors, etc on the Gonum suite of packages. diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/concrete.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/concrete.go new file mode 100644 index 000000000000..4b272a76c756 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/concrete.go @@ -0,0 +1,8 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete + +// TODO(anyone) Package level documentation for this describing the overall +// reason for the package and a summary for the provided types. diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/dense.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/dense.go new file mode 100644 index 000000000000..cc2352065ef9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/dense.go @@ -0,0 +1,151 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete + +import ( + "github.com/gonum/graph" +) + +// A dense graph is a graph such that all IDs are in a contiguous block from 0 to +// TheNumberOfNodes-1. It uses an adjacency matrix and should be relatively fast for both access +// and writing. +// +// This graph implements the CrunchGraph, but since it's naturally dense this is superfluous. +type DenseGraph struct { + adjacencyMatrix []float64 + numNodes int +} + +// Creates a dense graph with the proper number of nodes. If passable is true all nodes will have +// an edge with unit cost, otherwise every node will start unconnected (cost of +Inf). +func NewDenseGraph(numNodes int, passable bool) *DenseGraph { + g := &DenseGraph{adjacencyMatrix: make([]float64, numNodes*numNodes), numNodes: numNodes} + if passable { + for i := range g.adjacencyMatrix { + g.adjacencyMatrix[i] = 1 + } + } else { + for i := range g.adjacencyMatrix { + g.adjacencyMatrix[i] = inf + } + } + + return g +} + +func (g *DenseGraph) NodeExists(n graph.Node) bool { + return n.ID() < g.numNodes +} + +func (g *DenseGraph) Degree(n graph.Node) int { + deg := 0 + for i := 0; i < g.numNodes; i++ { + if g.adjacencyMatrix[i*g.numNodes+n.ID()] != inf { + deg++ + } + + if g.adjacencyMatrix[n.ID()*g.numNodes+i] != inf { + deg++ + } + } + + return deg +} + +func (g *DenseGraph) NodeList() []graph.Node { + nodes := make([]graph.Node, g.numNodes) + for i := 0; i < g.numNodes; i++ { + nodes[i] = Node(i) + } + + return nodes +} + +func (g *DenseGraph) DirectedEdgeList() []graph.Edge { + edges := make([]graph.Edge, 0, len(g.adjacencyMatrix)) + for i := 0; i < g.numNodes; i++ { + for j := 0; j < g.numNodes; j++ { + if g.adjacencyMatrix[i*g.numNodes+j] != inf { + edges = append(edges, Edge{Node(i), Node(j)}) + } + } + } + + return edges +} + +func (g *DenseGraph) Neighbors(n graph.Node) []graph.Node { + neighbors := make([]graph.Node, 0) + for i := 0; i < g.numNodes; i++ { + if g.adjacencyMatrix[i*g.numNodes+n.ID()] != inf || + g.adjacencyMatrix[n.ID()*g.numNodes+i] != inf { + neighbors = append(neighbors, Node(i)) + } + } + + return neighbors +} + +func (g *DenseGraph) EdgeBetween(n, neighbor graph.Node) graph.Edge { + if g.adjacencyMatrix[neighbor.ID()*g.numNodes+n.ID()] != inf || + g.adjacencyMatrix[n.ID()*g.numNodes+neighbor.ID()] != inf { + return Edge{n, neighbor} + } + + return nil +} + +func (g *DenseGraph) Successors(n graph.Node) []graph.Node { + neighbors := make([]graph.Node, 0) + for i := 0; i < g.numNodes; i++ { + if g.adjacencyMatrix[n.ID()*g.numNodes+i] != inf { + neighbors = append(neighbors, Node(i)) + } + } + + return neighbors +} + +func (g *DenseGraph) EdgeTo(n, succ graph.Node) graph.Edge { + if g.adjacencyMatrix[n.ID()*g.numNodes+succ.ID()] != inf { + return Edge{n, succ} + } + + return nil +} + +func (g *DenseGraph) Predecessors(n graph.Node) []graph.Node { + neighbors := make([]graph.Node, 0) + for i := 0; i < g.numNodes; i++ { + if g.adjacencyMatrix[i*g.numNodes+n.ID()] != inf { + neighbors = append(neighbors, Node(i)) + } + } + + return neighbors +} + +// DenseGraph is naturally dense, we don't need to do anything +func (g *DenseGraph) Crunch() { +} + +func (g *DenseGraph) Cost(e graph.Edge) float64 { + return g.adjacencyMatrix[e.Head().ID()*g.numNodes+e.Tail().ID()] +} + +// Sets the cost of an edge. If the cost is +Inf, it will remove the edge, +// if directed is true, it will only remove the edge one way. If it's false it will change the cost +// of the edge from succ to node as well. +func (g *DenseGraph) SetEdgeCost(e graph.Edge, cost float64, directed bool) { + g.adjacencyMatrix[e.Head().ID()*g.numNodes+e.Tail().ID()] = cost + if !directed { + g.adjacencyMatrix[e.Tail().ID()*g.numNodes+e.Head().ID()] = cost + } +} + +// Equivalent to SetEdgeCost(edge, math.Inf(1), directed) +func (g *DenseGraph) RemoveEdge(e graph.Edge, directed bool) { + g.SetEdgeCost(e, inf, directed) +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/densegraph_test.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/densegraph_test.go new file mode 100644 index 000000000000..b424cf1c4f02 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/densegraph_test.go @@ -0,0 +1,193 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete_test + +import ( + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" + "math" + "sort" + "testing" +) + +var _ graph.DirectedGraph = &concrete.DenseGraph{} +var _ graph.CrunchGraph = &concrete.DenseGraph{} + +func TestBasicDenseImpassable(t *testing.T) { + dg := concrete.NewDenseGraph(5, false) + if dg == nil { + t.Fatal("Directed graph could not be made") + } + + for i := 0; i < 5; i++ { + if !dg.NodeExists(concrete.Node(i)) { + t.Errorf("Node that should exist doesn't: %d", i) + } + + if degree := dg.Degree(concrete.Node(i)); degree != 0 { + t.Errorf("Node in impassable graph has a neighbor. Node: %d Degree: %d", i, degree) + } + } + + for i := 5; i < 10; i++ { + if dg.NodeExists(concrete.Node(i)) { + t.Errorf("Node exists that shouldn't: %d", i) + } + } +} + +func TestBasicDensePassable(t *testing.T) { + dg := concrete.NewDenseGraph(5, true) + if dg == nil { + t.Fatal("Directed graph could not be made") + } + + for i := 0; i < 5; i++ { + if !dg.NodeExists(concrete.Node(i)) { + t.Errorf("Node that should exist doesn't: %d", i) + } + + if degree := dg.Degree(concrete.Node(i)); degree != 10 { + t.Errorf("Node in impassable graph has a neighbor. Node: %d Degree: %d", i, degree) + } + } + + for i := 5; i < 10; i++ { + if dg.NodeExists(concrete.Node(i)) { + t.Errorf("Node exists that shouldn't: %d", i) + } + } +} + +func TestDenseAddRemove(t *testing.T) { + dg := concrete.NewDenseGraph(10, false) + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 1, false) + + if neighbors := dg.Neighbors(concrete.Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || + dg.EdgeBetween(concrete.Node(0), concrete.Node(2)) == nil { + t.Errorf("Couldn't add neighbor") + } + + if neighbors := dg.Successors(concrete.Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || + dg.EdgeTo(concrete.Node(0), concrete.Node(2)) == nil { + t.Errorf("Adding edge didn't create successor") + } + + if neighbors := dg.Predecessors(concrete.Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || + dg.EdgeTo(concrete.Node(2), concrete.Node(0)) == nil { + t.Errorf("Adding undirected edge didn't create predecessor") + } + + if neighbors := dg.Neighbors(concrete.Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || + dg.EdgeBetween(concrete.Node(2), concrete.Node(0)) == nil { + t.Errorf("Adding an undirected neighbor didn't add it reciprocally") + } + + if neighbors := dg.Successors(concrete.Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || + dg.EdgeTo(concrete.Node(2), concrete.Node(0)) == nil { + t.Errorf("Adding undirected edge didn't create proper successor") + } + + if neighbors := dg.Predecessors(concrete.Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || + dg.EdgeTo(concrete.Node(2), concrete.Node(0)) == nil { + t.Errorf("Adding edge didn't create proper predecessor") + } + + dg.RemoveEdge(concrete.Edge{concrete.Node(0), concrete.Node(2)}, true) + + if neighbors := dg.Neighbors(concrete.Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || + dg.EdgeBetween(concrete.Node(0), concrete.Node(2)) == nil { + t.Errorf("Removing a directed edge changed result of neighbors when neighbors is undirected; neighbors: %v", neighbors) + } + + if neighbors := dg.Successors(concrete.Node(0)); len(neighbors) != 0 || dg.EdgeTo(concrete.Node(0), concrete.Node(2)) != nil { + t.Errorf("Removing edge didn't properly remove successor") + } + + if neighbors := dg.Predecessors(concrete.Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || + dg.EdgeTo(concrete.Node(2), concrete.Node(0)) == nil { + t.Errorf("Removing directed edge improperly removed predecessor") + } + + if neighbors := dg.Neighbors(concrete.Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || + dg.EdgeBetween(concrete.Node(2), concrete.Node(0)) == nil { + t.Errorf("Removing a directed edge removed reciprocal edge, neighbors: %v", neighbors) + } + + if neighbors := dg.Successors(concrete.Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || + dg.EdgeTo(concrete.Node(2), concrete.Node(0)) == nil { + t.Errorf("Removing edge improperly removed successor") + } + + if neighbors := dg.Predecessors(concrete.Node(2)); len(neighbors) != 0 || dg.EdgeTo(concrete.Node(0), concrete.Node(2)) != nil { + t.Errorf("Removing directed edge wrongly kept predecessor") + } + + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2, true) + // I figure we've torture tested Neighbors/Successors/Predecessors at this point + // so we'll just use the bool functions now + if dg.EdgeTo(concrete.Node(0), concrete.Node(2)) == nil { + t.Error("Adding directed edge didn't change successor back") + } else if dg.EdgeTo(concrete.Node(2), concrete.Node(0)) == nil { + t.Error("Adding directed edge strangely removed reverse successor") + } else if c1, c2 := dg.Cost(concrete.Edge{concrete.Node(2), concrete.Node(0)}), dg.Cost(concrete.Edge{concrete.Node(0), concrete.Node(2)}); math.Abs(c1-c2) < .000001 { + t.Error("Adding directed edge affected cost in undirected manner") + } + + dg.RemoveEdge(concrete.Edge{concrete.Node(2), concrete.Node(0)}, false) + if dg.EdgeTo(concrete.Node(0), concrete.Node(2)) != nil || dg.EdgeTo(concrete.Node(2), concrete.Node(0)) != nil { + t.Error("Removing undirected edge did no work properly") + } +} + +type nodeSorter []graph.Node + +func (ns nodeSorter) Len() int { + return len(ns) +} + +func (ns nodeSorter) Swap(i, j int) { + ns[i], ns[j] = ns[j], ns[i] +} + +func (ns nodeSorter) Less(i, j int) bool { + return ns[i].ID() < ns[j].ID() +} + +func TestDenseLists(t *testing.T) { + dg := concrete.NewDenseGraph(15, true) + nodes := nodeSorter(dg.NodeList()) + + if len(nodes) != 15 { + t.Fatalf("Wrong number of nodes") + } + + sort.Sort(nodes) + + for i, node := range dg.NodeList() { + if i != node.ID() { + t.Errorf("Node list doesn't return properly id'd nodes") + } + } + + edges := dg.DirectedEdgeList() + if len(edges) != 15*15 { + t.Errorf("Improper number of edges for passable dense graph") + } + + dg.RemoveEdge(concrete.Edge{concrete.Node(12), concrete.Node(11)}, true) + edges = dg.DirectedEdgeList() + if len(edges) != (15*15)-1 { + t.Errorf("Removing edge didn't affect edge listing properly") + } +} + +func TestCrunch(t *testing.T) { + dg := concrete.NewDenseGraph(5, true) + dg.Crunch() + if len(dg.NodeList()) != 5 || len(dg.DirectedEdgeList()) != 5*5 { + t.Errorf("Crunch did something") + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutablegr.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutablegr.go new file mode 100644 index 000000000000..45eb3ed668ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutablegr.go @@ -0,0 +1,224 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete + +import ( + "github.com/gonum/graph" +) + +// A simple int alias. +type Node int + +func (n Node) ID() int { + return int(n) +} + +// Just a collection of two nodes +type Edge struct { + H, T graph.Node +} + +func (e Edge) Head() graph.Node { + return e.H +} + +func (e Edge) Tail() graph.Node { + return e.T +} + +type WeightedEdge struct { + graph.Edge + Cost float64 +} + +// A GonumGraph is a very generalized graph that can handle an arbitrary number of vertices and +// edges -- as well as act as either directed or undirected. +// +// Internally, it uses a map of successors AND predecessors, to speed up some operations (such as +// getting all successors/predecessors). It also speeds up things like adding edges (assuming both +// edges exist). +// +// However, its generality is also its weakness (and partially a flaw in needing to satisfy +// MutableGraph). For most purposes, creating your own graph is probably better. For instance, +// see TileGraph for an example of an immutable 2D grid of tiles that also implements the Graph +// interface, but would be more suitable if all you needed was a simple undirected 2D grid. +type Graph struct { + neighbors map[int]map[int]WeightedEdge + nodeMap map[int]graph.Node + + // Node add/remove convenience vars + maxID int + freeMap map[int]struct{} +} + +func NewGraph() *Graph { + return &Graph{ + neighbors: make(map[int]map[int]WeightedEdge), + nodeMap: make(map[int]graph.Node), + maxID: 0, + freeMap: make(map[int]struct{}), + } +} + +/* Mutable implementation */ + +func (g *Graph) NewNode() graph.Node { + if g.maxID != maxInt { + g.maxID++ + return Node(g.maxID) + } + + // Implicitly checks if len(g.freeMap) == 0 + for id := range g.freeMap { + return Node(id) + } + + // I cannot foresee this ever happening, but just in case, we check. + if len(g.nodeMap) == maxInt { + panic("cannot allocate node: graph too large") + } + + for i := 0; i < maxInt; i++ { + if _, ok := g.nodeMap[i]; !ok { + return Node(i) + } + } + + // Should not happen. + panic("cannot allocate node id: no free id found") +} + +func (g *Graph) AddNode(n graph.Node) { + g.nodeMap[n.ID()] = n + g.neighbors[n.ID()] = make(map[int]WeightedEdge) + + delete(g.freeMap, n.ID()) + g.maxID = max(g.maxID, n.ID()) +} + +func (g *Graph) AddUndirectedEdge(e graph.Edge, cost float64) { + head, tail := e.Head(), e.Tail() + if !g.NodeExists(head) { + g.AddNode(head) + } + + if !g.NodeExists(tail) { + g.AddNode(tail) + } + + g.neighbors[head.ID()][tail.ID()] = WeightedEdge{Edge: e, Cost: cost} + g.neighbors[tail.ID()][head.ID()] = WeightedEdge{Edge: e, Cost: cost} +} + +func (g *Graph) RemoveNode(n graph.Node) { + if _, ok := g.nodeMap[n.ID()]; !ok { + return + } + delete(g.nodeMap, n.ID()) + + for neigh := range g.neighbors[n.ID()] { + delete(g.neighbors[neigh], n.ID()) + } + delete(g.neighbors, n.ID()) + + if g.maxID != 0 && n.ID() == g.maxID { + g.maxID-- + } + g.freeMap[n.ID()] = struct{}{} +} + +func (g *Graph) RemoveUndirectedEdge(e graph.Edge) { + head, tail := e.Head(), e.Tail() + if _, ok := g.nodeMap[head.ID()]; !ok { + return + } else if _, ok := g.nodeMap[tail.ID()]; !ok { + return + } + + delete(g.neighbors[head.ID()], tail.ID()) + delete(g.neighbors[tail.ID()], head.ID()) +} + +func (g *Graph) EmptyGraph() { + g.neighbors = make(map[int]map[int]WeightedEdge) + g.nodeMap = make(map[int]graph.Node) +} + +/* Graph implementation */ + +func (g *Graph) Neighbors(n graph.Node) []graph.Node { + if !g.NodeExists(n) { + return nil + } + + neighbors := make([]graph.Node, len(g.neighbors[n.ID()])) + i := 0 + for id := range g.neighbors[n.ID()] { + neighbors[i] = g.nodeMap[id] + i++ + } + + return neighbors +} + +func (g *Graph) EdgeBetween(n, neigh graph.Node) graph.Edge { + // Don't need to check if neigh exists because + // it's implicit in the neighbors access. + if !g.NodeExists(n) { + return nil + } + + return g.neighbors[n.ID()][neigh.ID()] +} + +func (g *Graph) NodeExists(n graph.Node) bool { + _, ok := g.nodeMap[n.ID()] + + return ok +} + +func (g *Graph) NodeList() []graph.Node { + nodes := make([]graph.Node, len(g.nodeMap)) + i := 0 + for _, n := range g.nodeMap { + nodes[i] = n + i++ + } + + return nodes +} + +func (g *Graph) Cost(e graph.Edge) float64 { + if n, ok := g.neighbors[e.Head().ID()]; ok { + if we, ok := n[e.Tail().ID()]; ok { + return we.Cost + } + } + return inf +} + +func (g *Graph) EdgeList() []graph.Edge { + m := make(map[WeightedEdge]struct{}) + toReturn := make([]graph.Edge, 0) + + for _, neighs := range g.neighbors { + for _, we := range neighs { + if _, ok := m[we]; !ok { + m[we] = struct{}{} + toReturn = append(toReturn, we.Edge) + } + } + } + + return toReturn +} + +func (g *Graph) Degree(n graph.Node) int { + if _, ok := g.nodeMap[n.ID()]; !ok { + return 0 + } + + return len(g.neighbors[n.ID()]) +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutablegr_test.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutablegr_test.go new file mode 100644 index 000000000000..9b2f94b30e46 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutablegr_test.go @@ -0,0 +1,43 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete_test + +import ( + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" +) + +var _ graph.Graph = (*concrete.Graph)(nil) +var _ graph.Graph = (*concrete.Graph)(nil) + +func TestAssertMutableNotDirected(t *testing.T) { + var g graph.MutableGraph = concrete.NewGraph() + if _, ok := g.(graph.DirectedGraph); ok { + t.Fatal("concrete.Graph is directed, but a MutableGraph cannot safely be directed!") + } +} + +func TestMaxID(t *testing.T) { + g := concrete.NewGraph() + nodes := make(map[graph.Node]struct{}) + for i := concrete.Node(0); i < 3; i++ { + g.AddNode(i) + nodes[i] = struct{}{} + } + g.RemoveNode(concrete.Node(0)) + delete(nodes, concrete.Node(0)) + g.RemoveNode(concrete.Node(2)) + delete(nodes, concrete.Node(2)) + n := g.NewNode() + g.AddNode(n) + if !g.NodeExists(n) { + t.Error("added node does not exist in graph") + } + if _, exists := nodes[n]; exists { + t.Errorf("Created already existing node id: %v", n.ID()) + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutdir.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutdir.go new file mode 100644 index 000000000000..65ff05c3f608 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutdir.go @@ -0,0 +1,263 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete + +import ( + "github.com/gonum/graph" +) + +// A Directed graph is a highly generalized MutableDirectedGraph. +// +// In most cases it's likely more desireable to use a graph specific to your +// problem domain. +type DirectedGraph struct { + successors map[int]map[int]WeightedEdge + predecessors map[int]map[int]WeightedEdge + nodeMap map[int]graph.Node + + // Add/remove convenience variables + maxID int + freeMap map[int]struct{} +} + +func NewDirectedGraph() *DirectedGraph { + return &DirectedGraph{ + successors: make(map[int]map[int]WeightedEdge), + predecessors: make(map[int]map[int]WeightedEdge), + nodeMap: make(map[int]graph.Node), + maxID: 0, + freeMap: make(map[int]struct{}), + } +} + +/* Mutable Graph implementation */ + +func (g *DirectedGraph) NewNode() graph.Node { + if g.maxID != maxInt { + g.maxID++ + return Node(g.maxID) + } + + // Implicitly checks if len(g.freeMap) == 0 + for id := range g.freeMap { + return Node(id) + } + + // I cannot foresee this ever happening, but just in case + if len(g.nodeMap) == maxInt { + panic("cannot allocate node: graph too large") + } + + for i := 0; i < maxInt; i++ { + if _, ok := g.nodeMap[i]; !ok { + return Node(i) + } + } + + // Should not happen. + panic("cannot allocate node id: no free id found") +} + +// Adds a node to the graph. Implementation note: if you add a node close to or at +// the max int on your machine NewNode will become slower. +func (g *DirectedGraph) AddNode(n graph.Node) { + g.nodeMap[n.ID()] = n + g.successors[n.ID()] = make(map[int]WeightedEdge) + g.predecessors[n.ID()] = make(map[int]WeightedEdge) + + delete(g.freeMap, n.ID()) + g.maxID = max(g.maxID, n.ID()) +} + +func (g *DirectedGraph) AddDirectedEdge(e graph.Edge, cost float64) { + head, tail := e.Head(), e.Tail() + if !g.NodeExists(head) { + g.AddNode(head) + } + + if !g.NodeExists(tail) { + g.AddNode(tail) + } + + g.successors[head.ID()][tail.ID()] = WeightedEdge{Edge: e, Cost: cost} + g.predecessors[tail.ID()][head.ID()] = WeightedEdge{Edge: e, Cost: cost} +} + +func (g *DirectedGraph) RemoveNode(n graph.Node) { + if _, ok := g.nodeMap[n.ID()]; !ok { + return + } + delete(g.nodeMap, n.ID()) + + for succ := range g.successors[n.ID()] { + delete(g.predecessors[succ], n.ID()) + } + delete(g.successors, n.ID()) + + for pred := range g.predecessors[n.ID()] { + delete(g.successors[pred], n.ID()) + } + delete(g.predecessors, n.ID()) + + g.maxID-- // Fun facts: even if this ID doesn't exist this still works! + g.freeMap[n.ID()] = struct{}{} +} + +func (g *DirectedGraph) RemoveDirectedEdge(e graph.Edge) { + head, tail := e.Head(), e.Tail() + if _, ok := g.nodeMap[head.ID()]; !ok { + return + } else if _, ok := g.nodeMap[tail.ID()]; !ok { + return + } + + delete(g.successors[head.ID()], tail.ID()) + delete(g.predecessors[tail.ID()], head.ID()) +} + +func (g *DirectedGraph) EmptyGraph() { + g.successors = make(map[int]map[int]WeightedEdge) + g.predecessors = make(map[int]map[int]WeightedEdge) + g.nodeMap = make(map[int]graph.Node) +} + +/* Graph implementation */ + +func (g *DirectedGraph) Successors(n graph.Node) []graph.Node { + if _, ok := g.successors[n.ID()]; !ok { + return nil + } + + successors := make([]graph.Node, len(g.successors[n.ID()])) + i := 0 + for succ := range g.successors[n.ID()] { + successors[i] = g.nodeMap[succ] + i++ + } + + return successors +} + +func (g *DirectedGraph) EdgeTo(n, succ graph.Node) graph.Edge { + if _, ok := g.nodeMap[n.ID()]; !ok { + return nil + } else if _, ok := g.nodeMap[succ.ID()]; !ok { + return nil + } + + edge, ok := g.successors[n.ID()][succ.ID()] + if !ok { + return nil + } + return edge +} + +func (g *DirectedGraph) Predecessors(n graph.Node) []graph.Node { + if _, ok := g.successors[n.ID()]; !ok { + return nil + } + + predecessors := make([]graph.Node, len(g.predecessors[n.ID()])) + i := 0 + for succ := range g.predecessors[n.ID()] { + predecessors[i] = g.nodeMap[succ] + i++ + } + + return predecessors +} + +func (g *DirectedGraph) Neighbors(n graph.Node) []graph.Node { + if _, ok := g.successors[n.ID()]; !ok { + return nil + } + + neighbors := make([]graph.Node, len(g.predecessors[n.ID()])+len(g.successors[n.ID()])) + i := 0 + for succ := range g.successors[n.ID()] { + neighbors[i] = g.nodeMap[succ] + i++ + } + + for pred := range g.predecessors[n.ID()] { + // We should only add the predecessor if it wasn't already added from successors + if _, ok := g.successors[n.ID()][pred]; !ok { + neighbors[i] = g.nodeMap[pred] + i++ + } + } + + // Otherwise we overcount for self loops + neighbors = neighbors[:i] + + return neighbors +} + +func (g *DirectedGraph) EdgeBetween(n, neigh graph.Node) graph.Edge { + e := g.EdgeTo(n, neigh) + if e != nil { + return e + } + + e = g.EdgeTo(neigh, n) + if e != nil { + return e + } + + return nil +} + +func (g *DirectedGraph) NodeExists(n graph.Node) bool { + _, ok := g.nodeMap[n.ID()] + + return ok +} + +func (g *DirectedGraph) Degree(n graph.Node) int { + if _, ok := g.nodeMap[n.ID()]; !ok { + return 0 + } + + return len(g.successors[n.ID()]) + len(g.predecessors[n.ID()]) +} + +func (g *DirectedGraph) NodeList() []graph.Node { + nodes := make([]graph.Node, len(g.successors)) + i := 0 + for _, n := range g.nodeMap { + nodes[i] = n + i++ + } + + return nodes +} + +func (g *DirectedGraph) Cost(e graph.Edge) float64 { + if s, ok := g.successors[e.Head().ID()]; ok { + if we, ok := s[e.Tail().ID()]; ok { + return we.Cost + } + } + return inf +} + +func (g *DirectedGraph) EdgeList() []graph.Edge { + edgeList := make([]graph.Edge, 0, len(g.successors)) + edgeMap := make(map[int]map[int]struct{}, len(g.successors)) + for n, succMap := range g.successors { + edgeMap[n] = make(map[int]struct{}, len(succMap)) + for succ, edge := range succMap { + if doneMap, ok := edgeMap[succ]; ok { + if _, ok := doneMap[n]; ok { + continue + } + } + edgeList = append(edgeList, edge) + edgeMap[n][succ] = struct{}{} + } + } + + return edgeList +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutdir_test.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutdir_test.go new file mode 100644 index 000000000000..8050dcb6370e --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/mutdir_test.go @@ -0,0 +1,43 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete_test + +import ( + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" +) + +var _ graph.Graph = &concrete.DirectedGraph{} +var _ graph.DirectedGraph = &concrete.DirectedGraph{} +var _ graph.DirectedGraph = &concrete.DirectedGraph{} + +// Tests Issue #27 +func TestEdgeOvercounting(t *testing.T) { + g := generateDummyGraph() + + if neigh := g.Neighbors(concrete.Node(concrete.Node(2))); len(neigh) != 3 { + t.Errorf("Node 2 has incorrect number of neighbors got neighbors %v (count %d), expected 3 neighbors {0,1,2}", neigh, len(neigh)) + } +} + +func generateDummyGraph() *concrete.DirectedGraph { + nodes := [5]struct{ srcId, targetId int }{ + {2, 1}, + {2, 2}, + {1, 0}, + {2, 0}, + {0, 2}, + } + + g := concrete.NewDirectedGraph() + + for _, n := range nodes { + g.AddDirectedEdge(concrete.Edge{concrete.Node(n.srcId), concrete.Node(n.targetId)}, 1) + } + + return g +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/tilegraph.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/tilegraph.go new file mode 100644 index 000000000000..0ac83de447bb --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/tilegraph.go @@ -0,0 +1,234 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete + +import ( + "errors" + "strings" + + "github.com/gonum/graph" +) + +type TileGraph struct { + tiles []bool + numRows, numCols int +} + +func NewTileGraph(dimX, dimY int, isPassable bool) *TileGraph { + tiles := make([]bool, dimX*dimY) + if isPassable { + for i := range tiles { + tiles[i] = true + } + } + + return &TileGraph{ + tiles: tiles, + numRows: dimX, + numCols: dimY, + } +} + +func GenerateTileGraph(template string) (*TileGraph, error) { + rows := strings.Split(strings.TrimSpace(template), "\n") + + tiles := make([]bool, 0) + + colCheck := -1 + for _, colString := range rows { + colCount := 0 + cols := strings.NewReader(colString) + for cols.Len() != 0 { + colCount += 1 + ch, _, err := cols.ReadRune() + if err != nil { + return nil, errors.New("Error while reading rune from input string") + } + + switch ch { + case '\u2580': + tiles = append(tiles, false) + case ' ': + tiles = append(tiles, true) + default: + return nil, errors.New("Unrecognized character while reading input string") + } + } + + if colCheck == -1 { + colCheck = colCount + } else if colCheck != colCount { + return nil, errors.New("Jagged rows, cannot generate graph.") + } + } + + return &TileGraph{ + tiles: tiles, + numRows: len(rows), + numCols: colCheck, + }, nil +} + +func (g *TileGraph) SetPassability(row, col int, passability bool) { + loc := row*g.numCols + col + if loc >= len(g.tiles) || row < 0 || col < 0 { + return + } + + g.tiles[loc] = passability +} + +func (g *TileGraph) String() string { + var outString string + for r := 0; r < g.numRows; r++ { + for c := 0; c < g.numCols; c++ { + if g.tiles[r*g.numCols+c] == false { + outString += "\u2580" // Black square + } else { + outString += " " // Space + } + } + + outString += "\n" + } + + return outString[:len(outString)-1] // Kill final newline +} + +func (g *TileGraph) PathString(path []graph.Node) string { + if path == nil || len(path) == 0 { + return g.String() + } + + var outString string + for r := 0; r < g.numRows; r++ { + for c := 0; c < g.numCols; c++ { + if id := r*g.numCols + c; g.tiles[id] == false { + outString += "\u2580" // Black square + } else if id == path[0].ID() { + outString += "s" + } else if id == path[len(path)-1].ID() { + outString += "g" + } else { + toAppend := " " + for _, num := range path[1 : len(path)-1] { + if id == num.ID() { + toAppend = "♥" + } + } + outString += toAppend + } + } + + outString += "\n" + } + + return outString[:len(outString)-1] +} + +func (g *TileGraph) Dimensions() (rows, cols int) { + return g.numRows, g.numCols +} + +func (g *TileGraph) IDToCoords(id int) (row, col int) { + col = (id % g.numCols) + row = (id - col) / g.numCols + + return row, col +} + +func (g *TileGraph) CoordsToID(row, col int) int { + if row < 0 || row >= g.numRows || col < 0 || col >= g.numCols { + return -1 + } + + return row*g.numCols + col +} + +func (g *TileGraph) CoordsToNode(row, col int) graph.Node { + id := g.CoordsToID(row, col) + if id == -1 { + return nil + } + return Node(id) +} + +func (g *TileGraph) Neighbors(n graph.Node) []graph.Node { + id := n.ID() + if !g.NodeExists(n) { + return nil + } + + row, col := g.IDToCoords(id) + + neighbors := []graph.Node{g.CoordsToNode(row-1, col), g.CoordsToNode(row+1, col), g.CoordsToNode(row, col-1), g.CoordsToNode(row, col+1)} + realNeighbors := make([]graph.Node, 0, 4) // Will overallocate sometimes, but not by much. Not a big deal + for _, neigh := range neighbors { + if neigh != nil && g.tiles[neigh.ID()] == true { + realNeighbors = append(realNeighbors, neigh) + } + } + + return realNeighbors +} + +func (g *TileGraph) EdgeBetween(n, neigh graph.Node) graph.Edge { + if !g.NodeExists(n) || !g.NodeExists(neigh) { + return nil + } + + r1, c1 := g.IDToCoords(n.ID()) + r2, c2 := g.IDToCoords(neigh.ID()) + if (c1 == c2 && (r2 == r1+1 || r2 == r1-1)) || (r1 == r2 && (c2 == c1+1 || c2 == c1-1)) { + return Edge{n, neigh} + } + + return nil +} + +func (g *TileGraph) NodeExists(n graph.Node) bool { + id := n.ID() + return id >= 0 && id < len(g.tiles) && g.tiles[id] == true +} + +func (g *TileGraph) Degree(n graph.Node) int { + return len(g.Neighbors(n)) * 2 +} + +func (g *TileGraph) EdgeList() []graph.Edge { + edges := make([]graph.Edge, 0) + for id, passable := range g.tiles { + if !passable { + continue + } + + for _, succ := range g.Neighbors(Node(id)) { + edges = append(edges, Edge{Node(id), succ}) + } + } + + return edges +} + +func (g *TileGraph) NodeList() []graph.Node { + nodes := make([]graph.Node, 0) + for id, passable := range g.tiles { + if !passable { + continue + } + + nodes = append(nodes, Node(id)) + } + + return nodes +} + +func (g *TileGraph) Cost(e graph.Edge) float64 { + if edge := g.EdgeBetween(e.Head(), e.Tail()); edge != nil { + return 1 + } + + return inf +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/tilegraph_test.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/tilegraph_test.go new file mode 100644 index 000000000000..1878f37dc27e --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/tilegraph_test.go @@ -0,0 +1,108 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete_test + +import ( + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" +) + +var _ graph.Graph = (*concrete.TileGraph)(nil) + +func TestTileGraph(t *testing.T) { + tg := concrete.NewTileGraph(4, 4, false) + + if tg == nil || tg.String() != "▀▀▀▀\n▀▀▀▀\n▀▀▀▀\n▀▀▀▀" { + t.Fatal("Tile graph not generated correctly") + } + + tg.SetPassability(0, 1, true) + if tg == nil || tg.String() != "▀ ▀▀\n▀▀▀▀\n▀▀▀▀\n▀▀▀▀" { + t.Fatal("Passability set incorrectly") + } + + tg.SetPassability(0, 1, false) + if tg == nil || tg.String() != "▀▀▀▀\n▀▀▀▀\n▀▀▀▀\n▀▀▀▀" { + t.Fatal("Passability set incorrectly") + } + + tg.SetPassability(0, 1, true) + if tg == nil || tg.String() != "▀ ▀▀\n▀▀▀▀\n▀▀▀▀\n▀▀▀▀" { + t.Fatal("Passability set incorrectly") + } + + tg.SetPassability(0, 2, true) + if tg == nil || tg.String() != "▀ ▀\n▀▀▀▀\n▀▀▀▀\n▀▀▀▀" { + t.Fatal("Passability set incorrectly") + } + + tg.SetPassability(1, 2, true) + if tg == nil || tg.String() != "▀ ▀\n▀▀ ▀\n▀▀▀▀\n▀▀▀▀" { + t.Fatal("Passability set incorrectly") + } + + tg.SetPassability(2, 2, true) + if tg == nil || tg.String() != "▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀▀▀" { + t.Fatal("Passability set incorrectly") + } + + tg.SetPassability(3, 2, true) + if tg == nil || tg.String() != "▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀ ▀" { + t.Fatal("Passability set incorrectly") + } + + if tg2, err := concrete.GenerateTileGraph("▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀ ▀"); err != nil { + t.Error("Tile graph errored on interpreting valid template string\n▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀ ▀") + } else if tg2.String() != "▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀ ▀" { + t.Error("Tile graph failed to generate properly with input string\n▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀ ▀") + } + + if tg.CoordsToID(0, 0) != 0 { + t.Error("Coords to ID fails on 0,0") + } else if tg.CoordsToID(3, 3) != 15 { + t.Error("Coords to ID fails on 3,3") + } else if tg.CoordsToID(0, 3) != 3 { + t.Error("Coords to ID fails on 0,3") + } else if tg.CoordsToID(3, 0) != 12 { + t.Error("Coords to ID fails on 3,0") + } + + if r, c := tg.IDToCoords(0); r != 0 || c != 0 { + t.Error("ID to Coords fails on 0,0") + } else if r, c := tg.IDToCoords(15); r != 3 || c != 3 { + t.Error("ID to Coords fails on 3,3") + } else if r, c := tg.IDToCoords(3); r != 0 || c != 3 { + t.Error("ID to Coords fails on 0,3") + } else if r, c := tg.IDToCoords(12); r != 3 || c != 0 { + t.Error("ID to Coords fails on 3,0") + } + + if succ := tg.Neighbors(concrete.Node(0)); succ != nil || len(succ) != 0 { + t.Error("Successors for impassable tile not 0") + } + + if succ := tg.Neighbors(concrete.Node(2)); succ == nil || len(succ) != 2 { + t.Error("Incorrect number of successors for (0,2)") + } else { + for _, s := range succ { + if s.ID() != 1 && s.ID() != 6 { + t.Error("Successor for (0,2) neither (0,1) nor (1,2)") + } + } + } + + if tg.Degree(concrete.Node(2)) != 4 { + t.Error("Degree returns incorrect number for (0,2)") + } + if tg.Degree(concrete.Node(1)) != 2 { + t.Error("Degree returns incorrect number for (0,2)") + } + if tg.Degree(concrete.Node(0)) != 0 { + t.Error("Degree returns incorrect number for impassable tile (0,0)") + } + +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/concrete/util.go b/Godeps/_workspace/src/github.com/gonum/graph/concrete/util.go new file mode 100644 index 000000000000..40f6a39c9d8e --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/concrete/util.go @@ -0,0 +1,40 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package concrete + +import ( + "math" + + "github.com/gonum/graph" +) + +type nodeSorter []graph.Node + +func (ns nodeSorter) Less(i, j int) bool { + return ns[i].ID() < ns[j].ID() +} + +func (ns nodeSorter) Swap(i, j int) { + ns[i], ns[j] = ns[j], ns[i] +} + +func (ns nodeSorter) Len() int { + return len(ns) +} + +// The math package only provides explicitly sized max +// values. This ensures we get the max for the actual +// type int. +const maxInt int = int(^uint(0) >> 1) + +var inf = math.Inf(1) + +func max(a, b int) int { + if a > b { + return a + } else { + return b + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/doc.go b/Godeps/_workspace/src/github.com/gonum/graph/doc.go new file mode 100644 index 000000000000..c32a736b5fd7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/doc.go @@ -0,0 +1,38 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package graph implements functions and interfaces to deal with formal discrete graphs. It aims to +be first and foremost flexible, with speed as a strong second priority. + +In this package, graphs are taken to be directed, and undirected graphs are considered to be a +special case of directed graphs that happen to have reciprocal edges. Graphs are, by default, +unweighted, but functions that require weighted edges have several methods of dealing with this. +In order of precedence: + +1. These functions have an argument called Cost (and in some cases, HeuristicCost). If this is +present, it will always be used to determine the cost between two nodes. + +2. These functions will check if your graph implements the Coster (and/or HeuristicCoster) +interface. If this is present, and the Cost (or HeuristicCost) argument is nil, these functions +will be used. + +3. Finally, if no user data is supplied, it will use the functions UniformCost (always returns 1) +and/or NulLHeuristic (always returns 0). + +For information on the specification for Cost functions, please see the Coster interface. + +Finally, although the functions take in a Graph -- they will always use the correct behavior. +If your graph implements DirectedGraph, it will use Successors and Predecessors where applicable, +if undirected, it will use Neighbors instead. If it implements neither, it will scan the edge list +for successors and predecessors where applicable. (This is slow, you should always implement either +Directed or Undirected) + +This package will never modify a graph that is not Mutable (and the interface does not allow it to +do so). However, return values are free to be modified, so never pass a reference to your own edge +list or node list. It also guarantees that any nodes passed back to the user will be the same +nodes returned to it -- that is, it will never take a Node's ID and then wrap the ID in a new +struct and return that. You'll always get back your original data. +*/ +package graph diff --git a/Godeps/_workspace/src/github.com/gonum/graph/ex/fdpclust/gn.go b/Godeps/_workspace/src/github.com/gonum/graph/ex/fdpclust/gn.go new file mode 100644 index 000000000000..b4c2fda6eff1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/ex/fdpclust/gn.go @@ -0,0 +1,272 @@ +package main + +import ( + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" +) + +type GraphNode struct { + id int + neighbors []graph.Node + roots []*GraphNode +} + +func (g *GraphNode) NodeExists(n graph.Node) bool { + if n.ID() == g.id { + return true + } + + visited := map[int]struct{}{g.id: struct{}{}} + for _, root := range g.roots { + if root.ID() == n.ID() { + return true + } + + if root.nodeExists(n, visited) { + return true + } + } + + for _, neigh := range g.neighbors { + if neigh.ID() == n.ID() { + return true + } + + if gn, ok := neigh.(*GraphNode); ok { + if gn.nodeExists(n, visited) { + return true + } + } + } + + return false +} + +func (g *GraphNode) nodeExists(n graph.Node, visited map[int]struct{}) bool { + for _, root := range g.roots { + if _, ok := visited[root.ID()]; ok { + continue + } + + visited[root.ID()] = struct{}{} + if root.ID() == n.ID() { + return true + } + + if root.nodeExists(n, visited) { + return true + } + + } + + for _, neigh := range g.neighbors { + if _, ok := visited[neigh.ID()]; ok { + continue + } + + visited[neigh.ID()] = struct{}{} + if neigh.ID() == n.ID() { + return true + } + + if gn, ok := neigh.(*GraphNode); ok { + if gn.nodeExists(n, visited) { + return true + } + } + + } + + return false +} + +func (g *GraphNode) NodeList() []graph.Node { + toReturn := []graph.Node{g} + visited := map[int]struct{}{g.id: struct{}{}} + + for _, root := range g.roots { + toReturn = append(toReturn, root) + visited[root.ID()] = struct{}{} + + toReturn = root.nodeList(toReturn, visited) + } + + for _, neigh := range g.neighbors { + toReturn = append(toReturn, neigh) + visited[neigh.ID()] = struct{}{} + + if gn, ok := neigh.(*GraphNode); ok { + toReturn = gn.nodeList(toReturn, visited) + } + } + + return toReturn +} + +func (g *GraphNode) nodeList(list []graph.Node, visited map[int]struct{}) []graph.Node { + for _, root := range g.roots { + if _, ok := visited[root.ID()]; ok { + continue + } + visited[root.ID()] = struct{}{} + list = append(list, graph.Node(root)) + + list = root.nodeList(list, visited) + } + + for _, neigh := range g.neighbors { + if _, ok := visited[neigh.ID()]; ok { + continue + } + + list = append(list, neigh) + if gn, ok := neigh.(*GraphNode); ok { + list = gn.nodeList(list, visited) + } + } + + return list +} + +func (g *GraphNode) Neighbors(n graph.Node) []graph.Node { + if n.ID() == g.ID() { + return g.neighbors + } + + visited := map[int]struct{}{g.id: struct{}{}} + for _, root := range g.roots { + visited[root.ID()] = struct{}{} + + if result := root.findNeighbors(n, visited); result != nil { + return result + } + } + + for _, neigh := range g.neighbors { + visited[neigh.ID()] = struct{}{} + + if gn, ok := neigh.(*GraphNode); ok { + if result := gn.findNeighbors(n, visited); result != nil { + return result + } + } + } + + return nil +} + +func (g *GraphNode) findNeighbors(n graph.Node, visited map[int]struct{}) []graph.Node { + if n.ID() == g.ID() { + return g.neighbors + } + + for _, root := range g.roots { + if _, ok := visited[root.ID()]; ok { + continue + } + visited[root.ID()] = struct{}{} + + if result := root.findNeighbors(n, visited); result != nil { + return result + } + } + + for _, neigh := range g.neighbors { + if _, ok := visited[neigh.ID()]; ok { + continue + } + visited[neigh.ID()] = struct{}{} + + if gn, ok := neigh.(*GraphNode); ok { + if result := gn.findNeighbors(n, visited); result != nil { + return result + } + } + } + + return nil +} + +func (g *GraphNode) EdgeBetween(n, neighbor graph.Node) graph.Edge { + if n.ID() == g.id || neighbor.ID() == g.id { + for _, neigh := range g.neighbors { + if neigh.ID() == n.ID() || neigh.ID() == neighbor.ID() { + return concrete.Edge{g, neigh} + } + } + + return nil + } + + visited := map[int]struct{}{g.id: struct{}{}} + for _, root := range g.roots { + visited[root.ID()] = struct{}{} + if result := root.edgeBetween(n, neighbor, visited); result != nil { + return result + } + } + + for _, neigh := range g.neighbors { + visited[neigh.ID()] = struct{}{} + if gn, ok := neigh.(*GraphNode); ok { + if result := gn.edgeBetween(n, neighbor, visited); result != nil { + return result + } + } + } + + return nil +} + +func (g *GraphNode) edgeBetween(n, neighbor graph.Node, visited map[int]struct{}) graph.Edge { + if n.ID() == g.id || neighbor.ID() == g.id { + for _, neigh := range g.neighbors { + if neigh.ID() == n.ID() || neigh.ID() == neighbor.ID() { + return concrete.Edge{g, neigh} + } + } + + return nil + } + + for _, root := range g.roots { + if _, ok := visited[root.ID()]; ok { + continue + } + visited[root.ID()] = struct{}{} + if result := root.edgeBetween(n, neighbor, visited); result != nil { + return result + } + } + + for _, neigh := range g.neighbors { + if _, ok := visited[neigh.ID()]; ok { + continue + } + + visited[neigh.ID()] = struct{}{} + if gn, ok := neigh.(*GraphNode); ok { + if result := gn.edgeBetween(n, neighbor, visited); result != nil { + return result + } + } + } + + return nil +} + +func (g *GraphNode) ID() int { + return g.id +} + +func (g *GraphNode) AddNeighbor(n *GraphNode) { + g.neighbors = append(g.neighbors, graph.Node(n)) +} + +func (g *GraphNode) AddRoot(n *GraphNode) { + g.roots = append(g.roots, n) +} + +func NewGraphNode(id int) *GraphNode { + return &GraphNode{id: id, neighbors: make([]graph.Node, 0), roots: make([]*GraphNode, 0)} +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/ex/fdpclust/main.go b/Godeps/_workspace/src/github.com/gonum/graph/ex/fdpclust/main.go new file mode 100644 index 000000000000..46327dcf2465 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/ex/fdpclust/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "github.com/gonum/graph" + "github.com/gonum/graph/search" +) + +func main() { + // graph G { + G := NewGraphNode(0) + // e + e := NewGraphNode(1) + + // subgraph clusterA { + clusterA := NewGraphNode(2) + + // a -- b + a := NewGraphNode(3) + b := NewGraphNode(4) + a.AddNeighbor(b) + b.AddNeighbor(a) + clusterA.AddRoot(a) + clusterA.AddRoot(b) + + // subgraph clusterC { + clusterC := NewGraphNode(5) + // C -- D + C := NewGraphNode(6) + D := NewGraphNode(7) + C.AddNeighbor(D) + D.AddNeighbor(C) + + clusterC.AddRoot(C) + clusterC.AddRoot(D) + // } + clusterA.AddRoot(clusterC) + // } + + // subgraph clusterB { + clusterB := NewGraphNode(8) + + // d -- f + d := NewGraphNode(9) + f := NewGraphNode(10) + d.AddNeighbor(f) + f.AddNeighbor(d) + clusterB.AddRoot(d) + clusterB.AddRoot(f) + // } + + // d -- D + d.AddNeighbor(D) + D.AddNeighbor(d) + + // e -- clusterB + e.AddNeighbor(clusterB) + clusterB.AddNeighbor(e) + + // clusterC -- clusterB + clusterC.AddNeighbor(clusterB) + clusterB.AddNeighbor(clusterC) + + G.AddRoot(e) + G.AddRoot(clusterA) + G.AddRoot(clusterB) + // } + + path := []graph.Node{C, D, d, f} + if !search.IsPath(path, G) { + fmt.Println("Not working!") + } else { + fmt.Println("Working!") + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/graph.go b/Godeps/_workspace/src/github.com/gonum/graph/graph.go new file mode 100644 index 000000000000..4b9e6dc1f998 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/graph.go @@ -0,0 +1,207 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +// All a node needs to do is identify itself. This allows the user to pass in nodes more +// interesting than an int, but also allow us to reap the benefits of having a map-storable, +// comparable type. +type Node interface { + ID() int +} + +// Allows edges to do something more interesting that just be a group of nodes. While the methods +// are called Head and Tail, they are not considered directed unless the given interface specifies +// otherwise. +type Edge interface { + Head() Node + Tail() Node +} + +// A Graph implements the behavior of an undirected graph. +// +// All methods in Graph are implicitly undirected. Graph algorithms that care about directionality +// will intelligently choose the DirectedGraph behavior if that interface is also implemented, +// even if the function itself only takes in a Graph (or a super-interface of graph). +type Graph interface { + // NodeExists returns true when node is currently in the graph. + NodeExists(Node) bool + + // NodeList returns a list of all nodes in no particular order, useful for + // determining things like if a graph is fully connected. The caller is + // free to modify this list. Implementations should construct a new list + // and not return internal representation. + NodeList() []Node + + // Neighbors returns all nodes connected by any edge to this node. + Neighbors(Node) []Node + + // EdgeBetween returns an edge between node and neighbor such that + // Head is one argument and Tail is the other. If no + // such edge exists, this function returns nil. + EdgeBetween(node, neighbor Node) Edge +} + +// Directed graphs are characterized by having seperable Heads and Tails in their edges. +// That is, if node1 goes to node2, that does not necessarily imply that node2 goes to node1. +// +// While it's possible for a directed graph to have fully reciprocal edges (i.e. the graph is +// symmetric) -- it is not required to be. The graph is also required to implement Graph +// because in many cases it can be useful to know all neighbors regardless of direction. +type DirectedGraph interface { + Graph + // Successors gives the nodes connected by OUTBOUND edges. + // If the graph is an undirected graph, this set is equal to Predecessors. + Successors(Node) []Node + + // EdgeTo returns an edge between node and successor such that + // Head returns node and Tail returns successor, if no + // such edge exists, this function returns nil. + EdgeTo(node, successor Node) Edge + + // Predecessors gives the nodes connected by INBOUND edges. + // If the graph is an undirected graph, this set is equal to Successors. + Predecessors(Node) []Node +} + +// Returns all undirected edges in the graph +type EdgeLister interface { + EdgeList() []Edge +} + +type EdgeListGraph interface { + Graph + EdgeLister +} + +// Returns all directed edges in the graph. +type DirectedEdgeLister interface { + DirectedEdgeList() []Edge +} + +type DirectedEdgeListGraph interface { + Graph + DirectedEdgeLister +} + +// A crunch graph forces a sparse graph to become a dense graph. That is, if the node IDs are +// [1,4,9,7] it would "crunch" the ids into the contiguous block [0,1,2,3]. Order is not +// required to be preserved between the non-cruched and crunched instances (that means in +// the example above 0 may correspond to 4 or 7 or 9, not necessarily 1). +// +// All dense graphs must have the first ID as 0. +type CrunchGraph interface { + Graph + Crunch() +} + +// A Graph that implements Coster has an actual cost between adjacent nodes, also known as a +// weighted graph. If a graph implements coster and a function needs to read cost (e.g. A*), +// this function will take precedence over the Uniform Cost function (all weights are 1) if "nil" +// is passed in for the function argument. +// +// If the argument is nil, or the edge is invalid for some reason, this should return math.Inf(1) +type Coster interface { + Cost(Edge) float64 +} + +type CostGraph interface { + Coster + Graph +} + +type CostDirectedGraph interface { + Coster + DirectedGraph +} + +// A graph that implements HeuristicCoster implements a heuristic between any two given nodes. +// Like Coster, if a graph implements this and a function needs a heuristic cost (e.g. A*), this +// function will take precedence over the Null Heuristic (always returns 0) if "nil" is passed in +// for the function argument. If HeuristicCost is not intended to be used, it can be implemented as +// the null heuristic (always returns 0). +type HeuristicCoster interface { + // HeuristicCost returns a heuristic cost between any two nodes. + HeuristicCost(n1, n2 Node) float64 +} + +// A Mutable is a graph that can have arbitrary nodes and edges added or removed. +// +// Anything implementing Mutable is required to store the actual argument. So if AddNode(myNode) is +// called and later a user calls on the graph graph.NodeList(), the node added by AddNode must be +// an the exact node, not a new node with the same ID. +// +// In any case where conflict is possible (e.g. adding two nodes with the same ID), the later +// call always supercedes the earlier one. +// +// Functions will generally expect one of MutableGraph or MutableDirectedGraph and not Mutable +// itself. That said, any function that takes Mutable[x], the destination mutable should +// always be a different graph than the source. +type Mutable interface { + // NewNode returns a node with a unique arbitrary ID. + NewNode() Node + + // Adds a node to the graph. If this is called multiple times for the same ID, the newer node + // overwrites the old one. + AddNode(Node) + + // RemoveNode removes a node from the graph, as well as any edges + // attached to it. If no such node exists, this is a no-op, not an error. + RemoveNode(Node) +} + +// MutableGraph is an interface ensuring the implementation of the ability to construct +// an arbitrary undirected graph. It is very important to note that any implementation +// of MutableGraph absolutely cannot safely implement the DirectedGraph interface. +// +// A MutableGraph is required to store any Edge argument in the same way Mutable must +// store a Node argument -- any retrieval call is required to return the exact supplied edge. +// This is what makes it incompatible with DirectedGraph. +// +// The reasoning is this: if you call AddUndirectedEdge(Edge{head,tail}); you are required +// to return the exact edge passed in when a retrieval method (EdgeTo/EdgeBetween) is called. +// If I call EdgeTo(tail,head), this means that since the edge exists, and was added as +// Edge{head,tail} this function MUST return Edge{head,tail}. However, EdgeTo requires this +// be returned as Edge{tail,head}. Thus there's a conflict that cannot be resolved between the +// two interface requirements. +type MutableGraph interface { + CostGraph + Mutable + + // Like EdgeBetween in Graph, AddUndirectedEdge adds an edge between two nodes. + // If one or both nodes do not exist, the graph is expected to add them. However, + // if the nodes already exist it should NOT replace existing nodes with e.Head() or + // e.Tail(). Overwriting nodes should explicitly be done with another call to AddNode() + AddUndirectedEdge(e Edge, cost float64) + + // RemoveEdge clears the stored edge between two nodes. Calling this will never + // remove a node. If the edge does not exist this is a no-op, not an error. + RemoveUndirectedEdge(Edge) +} + +// MutableDirectedGraph is an interface that ensures one can construct an arbitrary directed +// graph. Naturally, a MutableDirectedGraph works for both undirected and directed cases, +// but simply using a MutableGraph may be cleaner. As the documentation for MutableGraph +// notes, however, a graph cannot safely implement MutableGraph and MutableDirectedGraph +// at the same time, because of the functionality of a EdgeTo in DirectedGraph. +type MutableDirectedGraph interface { + CostDirectedGraph + Mutable + + // Like EdgeTo in DirectedGraph, AddDirectedEdge adds an edge FROM head TO tail. + // If one or both nodes do not exist, the graph is expected to add them. However, + // if the nodes already exist it should NOT replace existing nodes with e.Head() or + // e.Tail(). Overwriting nodes should explicitly be done with another call to AddNode() + AddDirectedEdge(e Edge, cost float64) + + // Removes an edge FROM e.Head TO e.Tail. If no such edge exists, this is a no-op, + // not an error. + RemoveDirectedEdge(Edge) +} + +// A function that returns the cost of following an edge +type CostFunc func(Edge) float64 + +// Estimates the cost of travelling between two nodes +type HeuristicCostFunc func(Node, Node) float64 diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/disjoint.go b/Godeps/_workspace/src/github.com/gonum/graph/search/disjoint.go new file mode 100644 index 000000000000..6821a3db632d --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/disjoint.go @@ -0,0 +1,87 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +// A disjoint set is a collection of non-overlapping sets. That is, for any two sets in the +// disjoint set, their intersection is the empty set. +// +// A disjoint set has three principle operations: Make Set, Find, and Union. +// +// Make set creates a new set for an element (presuming it does not already exist in any set in +// the disjoint set), Find finds the set containing that element (if any), and Union merges two +// sets in the disjoint set. In general, algorithms operating on disjoint sets are "union-find" +// algorithms, where two sets are found with Find, and then joined with Union. +// +// A concrete example of a union-find algorithm can be found as discrete.Kruskal -- which unions +// two sets when an edge is created between two vertices, and refuses to make an edge between two +// vertices if they're part of the same set. +type disjointSet struct { + master map[int]*disjointSetNode +} + +type disjointSetNode struct { + parent *disjointSetNode + rank int +} + +func newDisjointSet() *disjointSet { + return &disjointSet{master: make(map[int]*disjointSetNode)} +} + +// If the element isn't already somewhere in there, adds it to the master set and its own tiny set. +func (ds *disjointSet) makeSet(e int) { + if _, ok := ds.master[e]; ok { + return + } + dsNode := &disjointSetNode{rank: 0} + dsNode.parent = dsNode + ds.master[e] = dsNode +} + +// Returns the set the element belongs to, or nil if none. +func (ds *disjointSet) find(e int) *disjointSetNode { + dsNode, ok := ds.master[e] + if !ok { + return nil + } + + return find(dsNode) +} + +func find(dsNode *disjointSetNode) *disjointSetNode { + if dsNode.parent != dsNode { + dsNode.parent = find(dsNode.parent) + } + + return dsNode.parent +} + +// Unions two subsets within the disjointSet. +// +// If x or y are not in this disjoint set, the behavior is undefined. If either pointer is nil, +// this function will panic. +func (ds *disjointSet) union(x, y *disjointSetNode) { + if x == nil || y == nil { + panic("Disjoint Set union on nil sets") + } + xRoot := find(x) + yRoot := find(y) + if xRoot == nil || yRoot == nil { + return + } + + if xRoot == yRoot { + return + } + + if xRoot.rank < yRoot.rank { + xRoot.parent = yRoot + } else if yRoot.rank < xRoot.rank { + yRoot.parent = xRoot + } else { + yRoot.parent = xRoot + xRoot.rank += 1 + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/disjointset_test.go b/Godeps/_workspace/src/github.com/gonum/graph/search/disjointset_test.go new file mode 100644 index 000000000000..72e908030427 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/disjointset_test.go @@ -0,0 +1,63 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +import ( + "testing" +) + +func TestDisjointSetMakeSet(t *testing.T) { + ds := newDisjointSet() + if ds.master == nil { + t.Fatal("Internal disjoint set map erroneously nil") + } else if len(ds.master) != 0 { + t.Error("Disjoint set master map of wrong size") + } + + ds.makeSet(3) + if len(ds.master) != 1 { + t.Error("Disjoint set master map of wrong size") + } + + if node, ok := ds.master[3]; !ok { + t.Error("Make set did not successfully add element") + } else { + if node == nil { + t.Fatal("Disjoint set node from makeSet is nil") + } + + if node.rank != 0 { + t.Error("Node rank set incorrectly") + } + + if node.parent != node { + t.Error("Node parent set incorrectly") + } + } +} + +func TestDisjointSetFind(t *testing.T) { + ds := newDisjointSet() + + ds.makeSet(3) + ds.makeSet(5) + + if ds.find(3) == ds.find(5) { + t.Error("Disjoint sets incorrectly found to be the same") + } +} + +func TestUnion(t *testing.T) { + ds := newDisjointSet() + + ds.makeSet(3) + ds.makeSet(5) + + ds.union(ds.find(3), ds.find(5)) + + if ds.find(3) != ds.find(5) { + t.Error("Sets found to be disjoint after union") + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/floydwarshall.go b/Godeps/_workspace/src/github.com/gonum/graph/search/floydwarshall.go new file mode 100644 index 000000000000..4efd5909f4bc --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/floydwarshall.go @@ -0,0 +1,230 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +import ( + "errors" + "math" + "sort" + + "github.com/gonum/graph" +) + +// Finds all shortest paths between start and goal +type AllPathFunc func(start, goal graph.Node) (path [][]graph.Node, cost float64, err error) + +// Finds one path between start and goal, which it finds is arbitrary +type PathFunc func(start, goal graph.Node) (path []graph.Node, cost float64, err error) + +// This function returns two functions: one that will generate all shortest paths between two +// nodes with ids i and j, and one that will generate just one path. +// +// This algorithm requires the CrunchGraph interface which means it only works on graphs with +// dense node ids since it uses an adjacency matrix. +// +// This algorithm isn't blazingly fast, but is relatively fast for the domain. It runs at +// O((number of vertices)^3) in best, worst, and average case, and successfully computes the cost +// between all pairs of vertices. +// +// This function operates slightly differently from the others for convenience -- rather than +// generating paths and returning them to you, it gives you the option of calling one of two +// functions for each start/goal pair you need info for. One will return the path, cost, or an +// error if no path exists. +// +// The other will return the cost and an error if no path exists, but it will also return ALL +// possible shortest paths between start and goal. This is not too much more expensive than +// generating one path, but it does obviously increase with the number of paths. +func FloydWarshall(g graph.CrunchGraph, cost graph.CostFunc) (AllPathFunc, PathFunc) { + g.Crunch() + sf := setupFuncs(g, cost, nil) + successors, isSuccessor, cost, edgeTo := sf.successors, sf.isSuccessor, sf.cost, sf.edgeTo + + nodes := denseNodeSorter(g.NodeList()) + sort.Sort(nodes) + numNodes := len(nodes) + + dist := make([]float64, numNodes*numNodes) + next := make([][]int, numNodes*numNodes) + for i := 0; i < numNodes; i++ { + for j := 0; j < numNodes; j++ { + if j != i { + dist[i+j*numNodes] = inf + } + } + } + + for _, node := range nodes { + for _, succ := range successors(node) { + dist[node.ID()+succ.ID()*numNodes] = cost(edgeTo(node, succ)) + } + } + + const thresh = 1e-5 + for k := 0; k < numNodes; k++ { + for i := 0; i < numNodes; i++ { + for j := 0; j < numNodes; j++ { + if dist[i+j*numNodes] > dist[i+k*numNodes]+dist[k+j*numNodes] { + dist[i+j*numNodes] = dist[i+k*numNodes] + dist[k+j*numNodes] + + // Avoid generating too much garbage by reusing the memory + // in the list if we've allocated one already + if next[i+j*numNodes] == nil { + next[i+j*numNodes] = []int{k} + } else { + next[i+j*numNodes] = next[i+j*numNodes][:1] + next[i+j*numNodes][0] = k + } + // If the cost between the nodes happens to be the same cost + // as what we know, add the approriate intermediary to the list + } else if i != k && i != j && j != k && math.Abs(dist[i+k*numNodes]+dist[k+j*numNodes]-dist[i+j*numNodes]) < thresh { + next[i+j*numNodes] = append(next[i+j*numNodes], k) + } + } + } + } + + return genAllPathsFunc(dist, next, nodes, g, cost, isSuccessor, edgeTo), genSinglePathFunc(dist, next, nodes) +} + +func genAllPathsFunc(dist []float64, next [][]int, nodes []graph.Node, g graph.Graph, cost graph.CostFunc, isSuccessor func(graph.Node, graph.Node) bool, edgeTo func(graph.Node, graph.Node) graph.Edge) func(start, goal graph.Node) ([][]graph.Node, float64, error) { + numNodes := len(nodes) + + // A recursive function to reconstruct all possible paths. + // It's not fast, but it's about as fast as can be reasonably expected + var allPathFinder func(i, j int) ([][]graph.Node, error) + allPathFinder = func(i, j int) ([][]graph.Node, error) { + if dist[i+j*numNodes] == inf { + return nil, errors.New("No path") + } + intermediates := next[i+j*numNodes] + if intermediates == nil || len(intermediates) == 0 { + // There is exactly one path + return [][]graph.Node{[]graph.Node{}}, nil + } + + toReturn := make([][]graph.Node, 0, len(intermediates)) + // Special case: if intermediates exist we need to explicitly check to see if i and j is also an optimal path + if isSuccessor(nodes[i], nodes[j]) && math.Abs(dist[i+j*numNodes]-cost(edgeTo(nodes[i], nodes[j]))) < .000001 { + toReturn = append(toReturn, []graph.Node{}) + } + + // This step is a tad convoluted: we have some list of intermediates. + // We can think of each intermediate as a path junction + // + // At this junction, we can find all the shortest paths back to i, + // and all the shortest paths down to j. Since this is a junction, + // any predecessor path that runs through this intermediate may + // freely choose any successor path to get to j. They'll all be + // of equivalent length. + // + // Thus, for each intermediate, we run through and join each predecessor + // path with each successor path via its junction. + for _, intermediate := range intermediates { + + // Find predecessors + preds, err := allPathFinder(i, intermediate) + if err != nil { + return nil, err + } + + // Join each predecessor with its junction + for a := range preds { + preds[a] = append(preds[a], nodes[intermediate]) + } + + // Find successors + succs, err := allPathFinder(intermediate, j) + if err != nil { + return nil, err + } + + // Join each successor with its predecessor at the junction. + // (the copying stuff is because slices are reference types) + for a := range succs { + for b := range preds { + path := make([]graph.Node, len(succs[a]), len(succs[a])+len(preds[b])) + copy(path, succs[a]) + path = append(path, preds[b]...) + toReturn = append(toReturn, path) + } + } + + } + + return toReturn, nil + } + + return func(start, goal graph.Node) ([][]graph.Node, float64, error) { + paths, err := allPathFinder(start.ID(), goal.ID()) + if err != nil { + return nil, inf, err + } + + for i := range paths { + // Prepend start and postpend goal, but don't repeat start/goal + + if len(paths[i]) != 0 { + if paths[i][0].ID() != start.ID() { + paths[i] = append(paths[i], nil) + copy(paths[i][1:], paths[i][:len(paths[i])-1]) + paths[i][0] = start + } + + if paths[i][len(paths[i])-1].ID() != goal.ID() { + paths[i] = append(paths[i], goal) + } + } else { + paths[i] = append(paths[i], start, goal) + } + } + + return paths, dist[start.ID()+goal.ID()*numNodes], nil + } +} + +func genSinglePathFunc(dist []float64, next [][]int, nodes []graph.Node) func(start, goal graph.Node) ([]graph.Node, float64, error) { + numNodes := len(nodes) + + var singlePathFinder func(i, j int) ([]graph.Node, error) + singlePathFinder = func(i, j int) ([]graph.Node, error) { + if dist[i+j*numNodes] == inf { + return nil, errors.New("No path") + } + + intermediates := next[i+j*numNodes] + if intermediates == nil || len(intermediates) == 0 { + return []graph.Node{}, nil + } + + intermediate := intermediates[0] + path, err := singlePathFinder(i, intermediate) + if err != nil { + return nil, err + } + + path = append(path, nodes[intermediate]) + p, err := singlePathFinder(intermediate, j) + if err != nil { + return nil, err + } + path = append(path, p...) + + return path, nil + } + + return func(start, goal graph.Node) ([]graph.Node, float64, error) { + path, err := singlePathFinder(start.ID(), goal.ID()) + if err != nil { + return nil, inf, err + } + + path = append(path, nil) + copy(path[1:], path[:len(path)-1]) + path[0] = start + path = append(path, goal) + + return path, dist[start.ID()+goal.ID()*numNodes], nil + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/floydwarshall_test.go b/Godeps/_workspace/src/github.com/gonum/graph/search/floydwarshall_test.go new file mode 100644 index 000000000000..746b677d58d1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/floydwarshall_test.go @@ -0,0 +1,182 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search_test + +import ( + "github.com/gonum/graph/concrete" + "github.com/gonum/graph/search" + "math" + "testing" +) + +func TestFWOneEdge(t *testing.T) { + dg := concrete.NewDenseGraph(2, true) + aPaths, sPath := search.FloydWarshall(dg, nil) + + path, cost, err := sPath(concrete.Node(0), concrete.Node(1)) + if err != nil { + t.Fatal(err) + } + + if math.Abs(cost-1) > 1e-6 { + t.Errorf("FW got wrong cost %f", cost) + } + + if len(path) != 2 || path[0].ID() != 0 && path[1].ID() != 1 { + t.Errorf("Wrong path in FW %v", path) + } + + paths, cost, err := aPaths(concrete.Node(0), concrete.Node(1)) + if err != nil { + t.Fatal(err) + } + + if math.Abs(cost-1) > 1e-6 { + t.Errorf("FW got wrong cost %f", cost) + } + + if len(paths) != 1 { + t.Errorf("Didn't get right paths in FW %v", paths) + } + + path = paths[0] + if len(path) != 2 || path[0].ID() != 0 && path[1].ID() != 1 { + t.Errorf("Wrong path in FW allpaths %v", path) + } +} + +func TestFWTwoPaths(t *testing.T) { + dg := concrete.NewDenseGraph(5, false) + // Adds two paths from 0->2 of equal length + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2, true) + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1, true) + dg.SetEdgeCost(concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1, true) + + aPaths, sPath := search.FloydWarshall(dg, nil) + path, cost, err := sPath(concrete.Node(0), concrete.Node(2)) + if err != nil { + t.Fatal(err) + } + + if math.Abs(cost-2) > .00001 { + t.Errorf("Path has incorrect cost, %f", cost) + } + + if len(path) == 2 && path[0].ID() == 0 && path[1].ID() == 2 { + t.Logf("Got correct path: %v", path) + } else if len(path) == 3 && path[0].ID() == 0 && path[1].ID() == 1 && path[2].ID() == 2 { + t.Logf("Got correct path %v", path) + } else { + t.Errorf("Got wrong path %v", path) + } + + paths, cost, err := aPaths(concrete.Node(0), concrete.Node(2)) + + if err != nil { + t.Fatal(err) + } + + if math.Abs(cost-2) > .00001 { + t.Errorf("All paths function gets incorrect cost, %f", cost) + } + + if len(paths) != 2 { + t.Fatalf("Didn't get all shortest paths %v", paths) + } + + for _, path := range paths { + if len(path) == 2 && path[0].ID() == 0 && path[1].ID() == 2 { + t.Logf("Got correct path for all paths: %v", path) + } else if len(path) == 3 && path[0].ID() == 0 && path[1].ID() == 1 && path[2].ID() == 2 { + t.Logf("Got correct path for all paths %v", path) + } else { + t.Errorf("Got wrong path for all paths %v", path) + } + } +} + +// Tests with multiple right paths, but also one dead-end path +// and one path that reaches the goal, but not optimally +func TestFWConfoundingPath(t *testing.T) { + dg := concrete.NewDenseGraph(6, false) + + // Add a path from 0->5 of cost 4 + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1, true) + dg.SetEdgeCost(concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1, true) + dg.SetEdgeCost(concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1, true) + dg.SetEdgeCost(concrete.Edge{concrete.Node(3), concrete.Node(5)}, 1, true) + + // Add direct edge to goal of cost 4 + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(5)}, 4, true) + + // Add edge to a node that's still optimal + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2, true) + + // Add edge to 3 that's overpriced + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(3)}, 4, true) + + // Add very cheap edge to 4 which is a dead end + dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(4)}, 0.25, true) + + aPaths, sPath := search.FloydWarshall(dg, nil) + + path, cost, err := sPath(concrete.Node(0), concrete.Node(5)) + if err != nil { + t.Fatal(err) + } + + if math.Abs(cost-4) > 1e-6 { + t.Errorf("Incorrect cost %f", cost) + } + + if len(path) == 5 && path[0].ID() == 0 && path[1].ID() == 1 && path[2].ID() == 2 && path[3].ID() == 3 && path[4].ID() == 5 { + t.Logf("Correct path found for single path %v", path) + } else if len(path) == 2 && path[0].ID() == 0 && path[1].ID() == 5 { + t.Logf("Correct path found for single path %v", path) + } else if len(path) == 4 && path[0].ID() == 0 && path[1].ID() == 2 && path[2].ID() == 3 && path[3].ID() == 5 { + t.Logf("Correct path found for single path %v", path) + } else { + t.Errorf("Wrong path found for single path %v", path) + } + + paths, cost, err := aPaths(concrete.Node(0), concrete.Node(5)) + if err != nil { + t.Fatal(err) + } + + if math.Abs(cost-4) > 1e-6 { + t.Errorf("Incorrect cost %f", cost) + } + + if len(paths) != 3 { + t.Errorf("Wrong paths gotten for all paths %v", paths) + } + + for _, path := range paths { + if len(path) == 5 && path[0].ID() == 0 && path[1].ID() == 1 && path[2].ID() == 2 && path[3].ID() == 3 && path[4].ID() == 5 { + t.Logf("Correct path found for multi path %v", path) + } else if len(path) == 2 && path[0].ID() == 0 && path[1].ID() == 5 { + t.Logf("Correct path found for multi path %v", path) + } else if len(path) == 4 && path[0].ID() == 0 && path[1].ID() == 2 && path[2].ID() == 3 && path[3].ID() == 5 { + t.Logf("Correct path found for multi path %v", path) + } else { + t.Errorf("Wrong path found for multi path %v", path) + } + } + + path, _, err = sPath(concrete.Node(4), concrete.Node(5)) + if err != nil { + t.Log("Success!", err) + } else { + t.Errorf("Path was found by FW single path where one shouldn't be %v", path) + } + + paths, _, err = aPaths(concrete.Node(4), concrete.Node(5)) + if err != nil { + t.Log("Success!", err) + } else { + t.Errorf("Path was found by FW multi-path where one shouldn't be %v", paths) + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/graph_search.go b/Godeps/_workspace/src/github.com/gonum/graph/search/graph_search.go new file mode 100644 index 000000000000..635488f681d7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/graph_search.go @@ -0,0 +1,674 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +import ( + "container/heap" + "errors" + "sort" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" +) + +// Returns an ordered list consisting of the nodes between start and goal. The path will be the +// shortest path assuming the function heuristicCost is admissible. The second return value is the +// cost, and the third is the number of nodes expanded while searching (useful info for tuning +// heuristics). Negative Costs will cause bad things to happen, as well as negative heuristic +// estimates. +// +// A heuristic is admissible if, for any node in the graph, the heuristic estimate of the cost +// between the node and the goal is less than or set to the true cost. +// +// Performance may be improved by providing a consistent heuristic (though one is not needed to +// find the optimal path), a heuristic is consistent if its value for a given node is less than +// (or equal to) the actual cost of reaching its neighbors + the heuristic estimate for the +// neighbor itself. You can force consistency by making your HeuristicCost function return +// max(NonConsistentHeuristicCost(neighbor,goal), NonConsistentHeuristicCost(self,goal) - +// Cost(self,neighbor)). If there are multiple neighbors, take the max of all of them. +// +// Cost and HeuristicCost take precedence for evaluating cost/heuristic distance. If one is not +// present (i.e. nil) the function will check the graph's interface for the respective interface: +// Coster for Cost and HeuristicCoster for HeuristicCost. If the correct one is present, it will +// use the graph's function for evaluation. +// +// Finally, if neither the argument nor the interface is present, the function will assume +// UniformCost for Cost and NullHeuristic for HeuristicCost. +// +// To run Uniform Cost Search, run A* with the NullHeuristic. +// +// To run Breadth First Search, run A* with both the NullHeuristic and UniformCost (or any cost +// function that returns a uniform positive value.) +func AStar(start, goal graph.Node, g graph.Graph, cost graph.CostFunc, heuristicCost graph.HeuristicCostFunc) (path []graph.Node, pathCost float64, nodesExpanded int) { + sf := setupFuncs(g, cost, heuristicCost) + successors, cost, heuristicCost, edgeTo := sf.successors, sf.cost, sf.heuristicCost, sf.edgeTo + + closedSet := make(map[int]internalNode) + openSet := &aStarPriorityQueue{nodes: make([]internalNode, 0), indexList: make(map[int]int)} + heap.Init(openSet) + node := internalNode{start, 0, heuristicCost(start, goal)} + heap.Push(openSet, node) + predecessor := make(map[int]graph.Node) + + for openSet.Len() != 0 { + curr := heap.Pop(openSet).(internalNode) + + nodesExpanded += 1 + + if curr.ID() == goal.ID() { + return rebuildPath(predecessor, goal), curr.gscore, nodesExpanded + } + + closedSet[curr.ID()] = curr + + for _, neighbor := range successors(curr.Node) { + if _, ok := closedSet[neighbor.ID()]; ok { + continue + } + + g := curr.gscore + cost(edgeTo(curr.Node, neighbor)) + + if existing, exists := openSet.Find(neighbor.ID()); !exists { + predecessor[neighbor.ID()] = curr + node = internalNode{neighbor, g, g + heuristicCost(neighbor, goal)} + heap.Push(openSet, node) + } else if g < existing.gscore { + predecessor[neighbor.ID()] = curr + openSet.Fix(neighbor.ID(), g, g+heuristicCost(neighbor, goal)) + } + } + } + + return nil, 0, nodesExpanded +} + +// BreadthFirstSearch finds a path with a minimal number of edges from from start to goal. +// +// BreadthFirstSearch returns the path found and the number of nodes visited in the search. +// The returned path is nil if no path exists. +func BreadthFirstSearch(start, goal graph.Node, g graph.Graph) ([]graph.Node, int) { + path, _, visited := AStar(start, goal, g, UniformCost, NullHeuristic) + return path, visited +} + +// Dijkstra's Algorithm is essentially a goalless Uniform Cost Search. That is, its results are +// roughly equivalent to running A* with the Null Heuristic from a single node to every other node +// in the graph -- though it's a fair bit faster because running A* in that way will recompute +// things it's already computed every call. Note that you won't necessarily get the same path +// you would get for A*, but the cost is guaranteed to be the same (that is, if multiple shortest +// paths exist, you may get a different shortest path). +// +// Like A*, Dijkstra's Algorithm likely won't run correctly with negative edge weights -- use +// Bellman-Ford for that instead. +// +// Dijkstra's algorithm usually only returns a cost map, however, since the data is available +// this version will also reconstruct the path to every node. +func Dijkstra(source graph.Node, g graph.Graph, cost graph.CostFunc) (paths map[int][]graph.Node, costs map[int]float64) { + + sf := setupFuncs(g, cost, nil) + successors, cost, edgeTo := sf.successors, sf.cost, sf.edgeTo + + nodes := g.NodeList() + openSet := &aStarPriorityQueue{nodes: make([]internalNode, 0), indexList: make(map[int]int)} + costs = make(map[int]float64, len(nodes)) // May overallocate, will change if it becomes a problem + predecessor := make(map[int]graph.Node, len(nodes)) + nodeIDMap := make(map[int]graph.Node, len(nodes)) + heap.Init(openSet) + + costs[source.ID()] = 0 + heap.Push(openSet, internalNode{source, 0, 0}) + + for openSet.Len() != 0 { + node := heap.Pop(openSet).(internalNode) + + nodeIDMap[node.ID()] = node + + for _, neighbor := range successors(node) { + tmpCost := costs[node.ID()] + cost(edgeTo(node, neighbor)) + if cost, ok := costs[neighbor.ID()]; !ok { + costs[neighbor.ID()] = tmpCost + predecessor[neighbor.ID()] = node + heap.Push(openSet, internalNode{neighbor, tmpCost, tmpCost}) + } else if tmpCost < cost { + costs[neighbor.ID()] = tmpCost + predecessor[neighbor.ID()] = node + openSet.Fix(neighbor.ID(), tmpCost, tmpCost) + } + } + } + + paths = make(map[int][]graph.Node, len(costs)) + for node := range costs { // Only reconstruct the path if one exists + paths[node] = rebuildPath(predecessor, nodeIDMap[node]) + } + return paths, costs +} + +// The Bellman-Ford Algorithm is the same as Dijkstra's Algorithm with a key difference. They both +// take a single source and find the shortest path to every other (reachable) node in the graph. +// Bellman-Ford, however, will detect negative edge loops and abort if one is present. A negative +// edge loop occurs when there is a cycle in the graph such that it can take an edge with a +// negative cost over and over. A -(-2)> B -(2)> C isn't a loop because A->B can only be taken once, +// but A<-(-2)->B-(2)>C is one because A and B have a bi-directional edge, and algorithms like +// Dijkstra's will infinitely flail between them getting progressively lower costs. +// +// That said, if you do not have a negative edge weight, use Dijkstra's Algorithm instead, because +// it's faster. +// +// Like Dijkstra's, along with the costs this implementation will also construct all the paths for +// you. In addition, it has a third return value which will be true if the algorithm was aborted +// due to the presence of a negative edge weight cycle. +func BellmanFord(source graph.Node, g graph.Graph, cost graph.CostFunc) (paths map[int][]graph.Node, costs map[int]float64, err error) { + sf := setupFuncs(g, cost, nil) + successors, cost, edgeTo := sf.successors, sf.cost, sf.edgeTo + + predecessor := make(map[int]graph.Node) + costs = make(map[int]float64) + nodeIDMap := make(map[int]graph.Node) + nodeIDMap[source.ID()] = source + costs[source.ID()] = 0 + nodes := g.NodeList() + + for i := 1; i < len(nodes)-1; i++ { + for _, node := range nodes { + nodeIDMap[node.ID()] = node + succs := successors(node) + for _, succ := range succs { + weight := cost(edgeTo(node, succ)) + nodeIDMap[succ.ID()] = succ + + if dist := costs[node.ID()] + weight; dist < costs[succ.ID()] { + costs[succ.ID()] = dist + predecessor[succ.ID()] = node + } + } + + } + } + + for _, node := range nodes { + for _, succ := range successors(node) { + weight := cost(edgeTo(node, succ)) + if costs[node.ID()]+weight < costs[succ.ID()] { + return nil, nil, errors.New("Negative edge cycle detected") + } + } + } + + paths = make(map[int][]graph.Node, len(costs)) + for node := range costs { + paths[node] = rebuildPath(predecessor, nodeIDMap[node]) + } + return paths, costs, nil +} + +// Johnson's Algorithm generates the lowest cost path between every pair of nodes in the graph. +// +// It makes use of Bellman-Ford and a dummy graph. It creates a dummy node containing edges with a +// cost of zero to every other node. Then it runs Bellman-Ford with this dummy node as the source. +// It then modifies the all the nodes' edge weights (which gets rid of all negative weights). +// +// Finally, it removes the dummy node and runs Dijkstra's starting at every node. +// +// This algorithm is fairly slow. Its purpose is to remove negative edge weights to allow +// Dijkstra's to function properly. It's probably not worth it to run this algorithm if you have +// all non-negative edge weights. Also note that this implementation copies your whole graph into +// a GonumGraph (so it can add/remove the dummy node and edges and reweight the graph). +// +// Its return values are, in order: a map from the source node, to the destination node, to the +// path between them; a map from the source node, to the destination node, to the cost of the path +// between them; and a bool that is true if Bellman-Ford detected a negative edge weight cycle -- +// thus causing it (and this algorithm) to abort (if aborted is true, both maps will be nil). +func Johnson(g graph.Graph, cost graph.CostFunc) (nodePaths map[int]map[int][]graph.Node, nodeCosts map[int]map[int]float64, err error) { + sf := setupFuncs(g, cost, nil) + successors, cost, edgeTo := sf.successors, sf.cost, sf.edgeTo + + /* Copy graph into a mutable one since it has to be altered for this algorithm */ + dummyGraph := concrete.NewDirectedGraph() + for _, node := range g.NodeList() { + neighbors := successors(node) + dummyGraph.NodeExists(node) + dummyGraph.AddNode(node) + for _, neighbor := range neighbors { + e := edgeTo(node, neighbor) + c := cost(e) + // Make a new edge with head and tail swapped; + // works due to the fact that we're not returning + // any edges in this so the contract doesn't need + // to be fulfilled. + if e.Head().ID() != node.ID() { + e = concrete.Edge{e.Tail(), e.Head()} + } + + dummyGraph.AddDirectedEdge(e, c) + } + } + + /* Step 1: Dummy node with 0 cost edge weights to every other node*/ + dummyNode := dummyGraph.NewNode() + dummyGraph.AddNode(dummyNode) + for _, node := range g.NodeList() { + dummyGraph.AddDirectedEdge(concrete.Edge{dummyNode, node}, 0) + } + + /* Step 2: Run Bellman-Ford starting at the dummy node, abort if it detects a cycle */ + _, costs, err := BellmanFord(dummyNode, dummyGraph, nil) + if err != nil { + return nil, nil, err + } + + /* Step 3: reweight the graph and remove the dummy node */ + for _, node := range g.NodeList() { + for _, succ := range successors(node) { + e := edgeTo(node, succ) + dummyGraph.AddDirectedEdge(e, cost(e)+costs[node.ID()]-costs[succ.ID()]) + } + } + + dummyGraph.RemoveNode(dummyNode) + + /* Step 4: Run Dijkstra's starting at every node */ + nodePaths = make(map[int]map[int][]graph.Node, len(g.NodeList())) + nodeCosts = make(map[int]map[int]float64) + + for _, node := range g.NodeList() { + nodePaths[node.ID()], nodeCosts[node.ID()] = Dijkstra(node, dummyGraph, nil) + } + + return nodePaths, nodeCosts, nil +} + +// Expands the first node it sees trying to find the destination. Depth First Search is *not* +// guaranteed to find the shortest path, however, if a path exists DFS is guaranteed to find it +// (provided you don't find a way to implement a Graph with an infinite depth.) +func DepthFirstSearch(start, goal graph.Node, g graph.Graph) []graph.Node { + sf := setupFuncs(g, nil, nil) + successors := sf.successors + + closedSet := make(intSet) + openSet := &nodeStack{start} + predecessor := make(map[int]graph.Node) + + for openSet.len() != 0 { + curr := openSet.pop() + + if closedSet.has(curr.ID()) { + continue + } + + if curr.ID() == goal.ID() { + return rebuildPath(predecessor, goal) + } + + closedSet.add(curr.ID()) + + for _, neighbor := range successors(curr) { + if closedSet.has(neighbor.ID()) { + continue + } + + predecessor[neighbor.ID()] = curr + openSet.push(neighbor) + } + } + + return nil +} + +// An admissible, consistent heuristic that won't speed up computation time at all. +func NullHeuristic(_, _ graph.Node) float64 { + return 0 +} + +// Assumes all edges in the graph have the same weight (including edges that don't exist!) +func UniformCost(e graph.Edge) float64 { + if e == nil { + return inf + } + + return 1 +} + +/* Simple operations */ + +// Copies a graph into the destination; maintaining all node IDs. The destination +// need not be empty, though overlapping node IDs and conflicting edges will overwrite +// existing data. +func CopyUndirectedGraph(dst graph.MutableGraph, src graph.Graph) { + cost := setupFuncs(src, nil, nil).cost + + for _, node := range src.NodeList() { + succs := src.Neighbors(node) + dst.AddNode(node) + for _, succ := range succs { + edge := src.EdgeBetween(node, succ) + dst.AddUndirectedEdge(edge, cost(edge)) + } + } + +} + +// Copies a graph into the destination; maintaining all node IDs. The destination +// need not be empty, though overlapping node IDs and conflicting edges will overwrite +// existing data. +func CopyDirectedGraph(dst graph.MutableDirectedGraph, src graph.DirectedGraph) { + cost := setupFuncs(src, nil, nil).cost + + for _, node := range src.NodeList() { + succs := src.Successors(node) + dst.AddNode(node) + for _, succ := range succs { + edge := src.EdgeTo(node, succ) + dst.AddDirectedEdge(edge, cost(edge)) + } + } + +} + +/* Basic Graph tests */ + +// Also known as Tarjan's Strongly Connected Components Algorithm. This returns all the strongly +// connected components in the graph. +// +// A strongly connected component of a graph is a set of vertices where it's possible to reach any +// vertex in the set from any other (meaning there's a cycle between them.) +// +// Generally speaking, a directed graph where the number of strongly connected components is equal +// to the number of nodes is acyclic, unless you count reflexive edges as a cycle (which requires +// only a little extra testing.) +// +// An undirected graph should end up with as many SCCs as there are "islands" (or subgraphs) of +// connections, meaning having more than one strongly connected component implies that your graph +// is not fully connected. +func Tarjan(g graph.Graph) [][]graph.Node { + nodes := g.NodeList() + t := tarjan{ + succ: setupFuncs(g, nil, nil).successors, + + indexTable: make(map[int]int, len(nodes)), + lowLink: make(map[int]int, len(nodes)), + onStack: make(intSet, len(nodes)), + } + for _, v := range nodes { + if t.indexTable[v.ID()] == 0 { + t.strongconnect(v) + } + } + return t.sccs +} + +// tarjan implements Tarjan's strongly connected component finding +// algorithm. The implementation is from the pseudocode at +// +// http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm?oldid=642744644 +// +type tarjan struct { + succ func(graph.Node) []graph.Node + + index int + indexTable map[int]int + lowLink map[int]int + onStack intSet + + stack []graph.Node + + sccs [][]graph.Node +} + +// strongconnect is the strongconnect function described in the +// wikipedia article. +func (t *tarjan) strongconnect(v graph.Node) { + vID := v.ID() + + // Set the depth index for v to the smallest unused index. + t.index++ + t.indexTable[vID] = t.index + t.lowLink[vID] = t.index + t.stack = append(t.stack, v) + t.onStack.add(vID) + + // Consider successors of v. + for _, w := range t.succ(v) { + wID := w.ID() + if t.indexTable[wID] == 0 { + // Successor w has not yet been visited; recur on it. + t.strongconnect(w) + t.lowLink[vID] = min(t.lowLink[vID], t.lowLink[wID]) + } else if t.onStack.has(wID) { + // Successor w is in stack s and hence in the current SCC. + t.lowLink[vID] = min(t.lowLink[vID], t.indexTable[wID]) + } + } + + // If v is a root node, pop the stack and generate an SCC. + if t.lowLink[vID] == t.indexTable[vID] { + // Start a new strongly connected component. + var ( + scc []graph.Node + w graph.Node + ) + for { + w, t.stack = t.stack[len(t.stack)-1], t.stack[:len(t.stack)-1] + t.onStack.remove(w.ID()) + // Add w to current strongly connected component. + scc = append(scc, w) + if w.ID() == vID { + break + } + } + // Output the current strongly connected component. + t.sccs = append(t.sccs, scc) + } +} + +// IsPath returns true for a connected path within a graph. +// +// IsPath returns true if, starting at path[0] and ending at path[len(path)-1], all nodes between +// are valid neighbors. That is, for each element path[i], path[i+1] is a valid successor. +// +// As special cases, IsPath returns true for a nil or zero length path, and for a path of length 1 +// (only one node) but only if the node listed in path exists within the graph. +// +// Graph must be non-nil. +func IsPath(path []graph.Node, g graph.Graph) bool { + isSuccessor := setupFuncs(g, nil, nil).isSuccessor + + if path == nil || len(path) == 0 { + return true + } else if len(path) == 1 { + return g.NodeExists(path[0]) + } + + for i := 0; i < len(path)-1; i++ { + if !isSuccessor(path[i], path[i+1]) { + return false + } + } + + return true +} + +/* Implements minimum-spanning tree algorithms; +puts the resulting minimum spanning tree in the dst graph */ + +// Generates a minimum spanning tree with sets. +// +// As with other algorithms that use Cost, the order of precedence is +// Argument > Interface > UniformCost. +// +// The destination must be empty (or at least disjoint with the node IDs of the input) +func Prim(dst graph.MutableGraph, g graph.EdgeListGraph, cost graph.CostFunc) { + sf := setupFuncs(g, cost, nil) + cost = sf.cost + + nlist := g.NodeList() + + if nlist == nil || len(nlist) == 0 { + return + } + + dst.AddNode(nlist[0]) + remainingNodes := make(intSet) + for _, node := range nlist[1:] { + remainingNodes.add(node.ID()) + } + + edgeList := g.EdgeList() + for remainingNodes.count() != 0 { + edgeWeights := make(edgeSorter, 0) + for _, edge := range edgeList { + if (dst.NodeExists(edge.Head()) && remainingNodes.has(edge.Tail().ID())) || + (dst.NodeExists(edge.Tail()) && remainingNodes.has(edge.Head().ID())) { + + edgeWeights = append(edgeWeights, concrete.WeightedEdge{Edge: edge, Cost: cost(edge)}) + } + } + + sort.Sort(edgeWeights) + myEdge := edgeWeights[0] + + dst.AddUndirectedEdge(myEdge.Edge, myEdge.Cost) + remainingNodes.remove(myEdge.Edge.Head().ID()) + } + +} + +// Generates a minimum spanning tree for a graph using discrete.DisjointSet. +// +// As with other algorithms with Cost, the precedence goes Argument > Interface > UniformCost. +// +// The destination must be empty (or at least disjoint with the node IDs of the input) +func Kruskal(dst graph.MutableGraph, g graph.EdgeListGraph, cost graph.CostFunc) { + cost = setupFuncs(g, cost, nil).cost + + edgeList := g.EdgeList() + edgeWeights := make(edgeSorter, 0, len(edgeList)) + for _, edge := range edgeList { + edgeWeights = append(edgeWeights, concrete.WeightedEdge{Edge: edge, Cost: cost(edge)}) + } + + sort.Sort(edgeWeights) + + ds := newDisjointSet() + for _, node := range g.NodeList() { + ds.makeSet(node.ID()) + } + + for _, edge := range edgeWeights { + // The disjoint set doesn't really care for which is head and which is tail so this + // should work fine without checking both ways + if s1, s2 := ds.find(edge.Edge.Head().ID()), ds.find(edge.Edge.Tail().ID()); s1 != s2 { + ds.union(s1, s2) + dst.AddUndirectedEdge(edge.Edge, edge.Cost) + } + } +} + +/* Control flow graph stuff */ + +// A dominates B if and only if the only path through B travels through A. +// +// This returns all possible dominators for all nodes, it does not prune for strict dominators, +// immediate dominators etc. +// +func Dominators(start graph.Node, g graph.Graph) map[int]Set { + allNodes := make(Set) + nlist := g.NodeList() + dominators := make(map[int]Set, len(nlist)) + for _, node := range nlist { + allNodes.add(node) + } + + predecessors := setupFuncs(g, nil, nil).predecessors + + for _, node := range nlist { + dominators[node.ID()] = make(Set) + if node.ID() == start.ID() { + dominators[node.ID()].add(start) + } else { + dominators[node.ID()].copy(allNodes) + } + } + + for somethingChanged := true; somethingChanged; { + somethingChanged = false + for _, node := range nlist { + if node.ID() == start.ID() { + continue + } + preds := predecessors(node) + if len(preds) == 0 { + continue + } + tmp := make(Set).copy(dominators[preds[0].ID()]) + for _, pred := range preds[1:] { + tmp.intersect(tmp, dominators[pred.ID()]) + } + + dom := make(Set) + dom.add(node) + + dom.union(dom, tmp) + if !equal(dom, dominators[node.ID()]) { + dominators[node.ID()] = dom + somethingChanged = true + } + } + } + + return dominators +} + +// A Postdominates B if and only if all paths from B travel through A. +// +// This returns all possible post-dominators for all nodes, it does not prune for strict +// postdominators, immediate postdominators etc. +func PostDominators(end graph.Node, g graph.Graph) map[int]Set { + successors := setupFuncs(g, nil, nil).successors + + allNodes := make(Set) + nlist := g.NodeList() + dominators := make(map[int]Set, len(nlist)) + for _, node := range nlist { + allNodes.add(node) + } + + for _, node := range nlist { + dominators[node.ID()] = make(Set) + if node.ID() == end.ID() { + dominators[node.ID()].add(end) + } else { + dominators[node.ID()].copy(allNodes) + } + } + + for somethingChanged := true; somethingChanged; { + somethingChanged = false + for _, node := range nlist { + if node.ID() == end.ID() { + continue + } + succs := successors(node) + if len(succs) == 0 { + continue + } + tmp := make(Set).copy(dominators[succs[0].ID()]) + for _, succ := range succs[1:] { + tmp.intersect(tmp, dominators[succ.ID()]) + } + + dom := make(Set) + dom.add(node) + + dom.union(dom, tmp) + if !equal(dom, dominators[node.ID()]) { + dominators[node.ID()] = dom + somethingChanged = true + } + } + } + + return dominators +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/internals.go b/Godeps/_workspace/src/github.com/gonum/graph/search/internals.go new file mode 100644 index 000000000000..c2e2d7685122 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/internals.go @@ -0,0 +1,233 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +import ( + "container/heap" + "math" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" +) + +var inf = math.Inf(1) + +type searchFuncs struct { + successors, predecessors, neighbors func(graph.Node) []graph.Node + isSuccessor, isPredecessor, isNeighbor func(graph.Node, graph.Node) bool + cost graph.CostFunc + heuristicCost graph.HeuristicCostFunc + edgeTo, edgeBetween func(graph.Node, graph.Node) graph.Edge +} + +func genIsSuccessor(g graph.DirectedGraph) func(graph.Node, graph.Node) bool { + return func(node, succ graph.Node) bool { + return g.EdgeTo(node, succ) != nil + } +} + +func genIsPredecessor(g graph.DirectedGraph) func(graph.Node, graph.Node) bool { + return func(node, succ graph.Node) bool { + return g.EdgeTo(succ, node) != nil + } +} + +func genIsNeighbor(g graph.Graph) func(graph.Node, graph.Node) bool { + return func(node, succ graph.Node) bool { + return g.EdgeBetween(succ, node) != nil + } +} + +// Sets up the cost functions and successor functions so I don't have to do a type switch every +// time. This almost always does more work than is necessary, but since it's only executed once +// per function, and graph functions are rather costly, the "extra work" should be negligible. +func setupFuncs(g graph.Graph, cost graph.CostFunc, heuristicCost graph.HeuristicCostFunc) searchFuncs { + + sf := searchFuncs{} + + switch g := g.(type) { + case graph.DirectedGraph: + sf.successors = g.Successors + sf.predecessors = g.Predecessors + sf.neighbors = g.Neighbors + sf.isSuccessor = genIsSuccessor(g) + sf.isPredecessor = genIsPredecessor(g) + sf.isNeighbor = genIsNeighbor(g) + sf.edgeBetween = g.EdgeBetween + sf.edgeTo = g.EdgeTo + default: + sf.successors = g.Neighbors + sf.predecessors = g.Neighbors + sf.neighbors = g.Neighbors + isNeighbor := genIsNeighbor(g) + sf.isSuccessor = isNeighbor + sf.isPredecessor = isNeighbor + sf.isNeighbor = isNeighbor + sf.edgeBetween = g.EdgeBetween + sf.edgeTo = g.EdgeBetween + } + + if heuristicCost != nil { + sf.heuristicCost = heuristicCost + } else { + if g, ok := g.(graph.HeuristicCoster); ok { + sf.heuristicCost = g.HeuristicCost + } else { + sf.heuristicCost = NullHeuristic + } + } + + if cost != nil { + sf.cost = cost + } else { + if g, ok := g.(graph.Coster); ok { + sf.cost = g.Cost + } else { + sf.cost = UniformCost + } + } + + return sf +} + +/** Sorts a list of edges by weight, agnostic to repeated edges as well as direction **/ + +type edgeSorter []concrete.WeightedEdge + +func (e edgeSorter) Len() int { + return len(e) +} + +func (e edgeSorter) Less(i, j int) bool { + return e[i].Cost < e[j].Cost +} + +func (e edgeSorter) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +/** Keeps track of a node's scores so they can be used in a priority queue for A* **/ + +type internalNode struct { + graph.Node + gscore, fscore float64 +} + +/* A* stuff */ +type aStarPriorityQueue struct { + indexList map[int]int + nodes []internalNode +} + +func (pq *aStarPriorityQueue) Less(i, j int) bool { + // As the heap documentation says, a priority queue is listed if the actual values + // are treated as if they were negative + return pq.nodes[i].fscore < pq.nodes[j].fscore +} + +func (pq *aStarPriorityQueue) Swap(i, j int) { + pq.indexList[pq.nodes[i].ID()] = j + pq.indexList[pq.nodes[j].ID()] = i + + pq.nodes[i], pq.nodes[j] = pq.nodes[j], pq.nodes[i] +} + +func (pq *aStarPriorityQueue) Len() int { + return len(pq.nodes) +} + +func (pq *aStarPriorityQueue) Push(x interface{}) { + node := x.(internalNode) + pq.nodes = append(pq.nodes, node) + pq.indexList[node.ID()] = len(pq.nodes) - 1 +} + +func (pq *aStarPriorityQueue) Pop() interface{} { + x := pq.nodes[len(pq.nodes)-1] + pq.nodes = pq.nodes[:len(pq.nodes)-1] + delete(pq.indexList, x.ID()) + + return x +} + +func (pq *aStarPriorityQueue) Fix(id int, newGScore, newFScore float64) { + if i, ok := pq.indexList[id]; ok { + pq.nodes[i].gscore = newGScore + pq.nodes[i].fscore = newFScore + heap.Fix(pq, i) + } +} + +func (pq *aStarPriorityQueue) Find(id int) (internalNode, bool) { + loc, ok := pq.indexList[id] + if ok { + return pq.nodes[loc], true + } else { + return internalNode{}, false + } + +} + +func (pq *aStarPriorityQueue) Exists(id int) bool { + _, ok := pq.indexList[id] + return ok +} + +type denseNodeSorter []graph.Node + +func (dns denseNodeSorter) Less(i, j int) bool { + return dns[i].ID() < dns[j].ID() +} + +func (dns denseNodeSorter) Swap(i, j int) { + dns[i], dns[j] = dns[j], dns[i] +} + +func (dns denseNodeSorter) Len() int { + return len(dns) +} + +// General utility funcs + +// Rebuilds a path backwards from the goal. +func rebuildPath(predecessors map[int]graph.Node, goal graph.Node) []graph.Node { + if n, ok := goal.(internalNode); ok { + goal = n.Node + } + path := []graph.Node{goal} + curr := goal + for prev, ok := predecessors[curr.ID()]; ok; prev, ok = predecessors[curr.ID()] { + if n, ok := prev.(internalNode); ok { + prev = n.Node + } + path = append(path, prev) + curr = prev + } + + // Reverse the path since it was built backwards + for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { + path[i], path[j] = path[j], path[i] + } + + return path +} + +type nodeStack []graph.Node + +func (s *nodeStack) len() int { return len(*s) } +func (s *nodeStack) pop() graph.Node { + v := *s + v, n := v[:len(v)-1], v[len(v)-1] + *s = v + return n +} +func (s *nodeStack) push(n graph.Node) { *s = append(*s, n) } + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/search_test.go b/Godeps/_workspace/src/github.com/gonum/graph/search/search_test.go new file mode 100644 index 000000000000..3fcce6b4ebe8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/search_test.go @@ -0,0 +1,422 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search_test + +import ( + "fmt" + "math" + "reflect" + "sort" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" + "github.com/gonum/graph/search" +) + +func TestSimpleAStar(t *testing.T) { + tg, err := concrete.GenerateTileGraph("▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀ ▀") + if err != nil { + t.Fatal("Couldn't generate tilegraph") + } + + path, cost, _ := search.AStar(concrete.Node(1), concrete.Node(14), tg, nil, nil) + if math.Abs(cost-4) > 1e-5 { + t.Errorf("A* reports incorrect cost for simple tilegraph search") + } + + if path == nil { + t.Fatalf("A* fails to find path for for simple tilegraph search") + } else { + correctPath := []int{1, 2, 6, 10, 14} + if len(path) != len(correctPath) { + t.Fatalf("Astar returns wrong length path for simple tilegraph search") + } + for i, node := range path { + if node.ID() != correctPath[i] { + t.Errorf("Astar returns wrong path at step", i, "got:", node, "actual:", correctPath[i]) + } + } + } +} + +func TestBiggerAStar(t *testing.T) { + tg := concrete.NewTileGraph(3, 3, true) + + path, cost, _ := search.AStar(concrete.Node(0), concrete.Node(8), tg, nil, nil) + + if math.Abs(cost-4) > 1e-5 || !search.IsPath(path, tg) { + t.Error("Non-optimal or impossible path found for 3x3 grid") + } + + tg = concrete.NewTileGraph(1000, 1000, true) + path, cost, _ = search.AStar(concrete.Node(0), concrete.Node(999*1000+999), tg, nil, nil) + if !search.IsPath(path, tg) || cost != 1998 { + t.Error("Non-optimal or impossible path found for 100x100 grid; cost:", cost, "path:\n"+tg.PathString(path)) + } +} + +func TestObstructedAStar(t *testing.T) { + tg := concrete.NewTileGraph(10, 10, true) + + // Creates a partial "wall" down the middle row with a gap down the left side + tg.SetPassability(4, 1, false) + tg.SetPassability(4, 2, false) + tg.SetPassability(4, 3, false) + tg.SetPassability(4, 4, false) + tg.SetPassability(4, 5, false) + tg.SetPassability(4, 6, false) + tg.SetPassability(4, 7, false) + tg.SetPassability(4, 8, false) + tg.SetPassability(4, 9, false) + + rows, cols := tg.Dimensions() + path, cost1, expanded := search.AStar(concrete.Node(5), tg.CoordsToNode(rows-1, cols-1), tg, nil, nil) + + if !search.IsPath(path, tg) { + t.Error("Path doesn't exist in obstructed graph") + } + + ManhattanHeuristic := func(n1, n2 graph.Node) float64 { + id1, id2 := n1.ID(), n2.ID() + r1, c1 := tg.IDToCoords(id1) + r2, c2 := tg.IDToCoords(id2) + + return math.Abs(float64(r1)-float64(r2)) + math.Abs(float64(c1)-float64(c2)) + } + + path, cost2, expanded2 := search.AStar(concrete.Node(5), tg.CoordsToNode(rows-1, cols-1), tg, nil, ManhattanHeuristic) + if !search.IsPath(path, tg) { + t.Error("Path doesn't exist when using heuristic on obstructed graph") + } + + if math.Abs(cost1-cost2) > 1e-5 { + t.Error("Cost when using admissible heuristic isn't approximately equal to cost without it") + } + + if expanded2 > expanded { + t.Error("Using admissible, consistent heuristic expanded more nodes than null heuristic (possible, but unlikely -- suggests an error somewhere)") + } + +} + +func TestNoPathAStar(t *testing.T) { + tg := concrete.NewTileGraph(5, 5, true) + + // Creates a "wall" down the middle row + tg.SetPassability(2, 0, false) + tg.SetPassability(2, 1, false) + tg.SetPassability(2, 2, false) + tg.SetPassability(2, 3, false) + tg.SetPassability(2, 4, false) + + rows, _ := tg.Dimensions() + path, _, _ := search.AStar(tg.CoordsToNode(0, 2), tg.CoordsToNode(rows-1, 2), tg, nil, nil) + + if len(path) > 0 { // Note that a nil slice will return len of 0, this won't panic + t.Error("A* finds path where none exists") + } +} + +func TestSmallAStar(t *testing.T) { + gg := newSmallGonumGraph() + heur := newSmallHeuristic() + if ok, edge, goal := monotonic(gg, heur); !ok { + t.Fatalf("non-monotonic heuristic. edge: %v goal: %v", edge, goal) + } + for _, start := range gg.NodeList() { + // get reference paths by Dijkstra + dPaths, dCosts := search.Dijkstra(start, gg, nil) + // assert that AStar finds each path + for goalID, dPath := range dPaths { + exp := fmt.Sprintln(dPath, dCosts[goalID]) + aPath, aCost, _ := search.AStar(start, concrete.Node(goalID), gg, nil, heur) + got := fmt.Sprintln(aPath, aCost) + if got != exp { + t.Error("expected", exp, "got", got) + } + } + } +} + +func ExampleBreadthFirstSearch() { + g := concrete.NewDirectedGraph() + var n0, n1, n2, n3 concrete.Node = 0, 1, 2, 3 + g.AddDirectedEdge(concrete.Edge{n0, n1}, 1) + g.AddDirectedEdge(concrete.Edge{n0, n2}, 1) + g.AddDirectedEdge(concrete.Edge{n2, n3}, 1) + path, v := search.BreadthFirstSearch(n0, n3, g) + fmt.Println("path:", path) + fmt.Println("nodes visited:", v) + // Output: + // path: [0 2 3] + // nodes visited: 4 +} + +func newSmallGonumGraph() *concrete.Graph { + eds := []struct{ n1, n2, edgeCost int }{ + {1, 2, 7}, + {1, 3, 9}, + {1, 6, 14}, + {2, 3, 10}, + {2, 4, 15}, + {3, 4, 11}, + {3, 6, 2}, + {4, 5, 7}, + {5, 6, 9}, + } + g := concrete.NewGraph() + for n := concrete.Node(1); n <= 6; n++ { + g.AddNode(n) + } + for _, ed := range eds { + e := concrete.Edge{ + concrete.Node(ed.n1), + concrete.Node(ed.n2), + } + g.AddUndirectedEdge(e, float64(ed.edgeCost)) + } + return g +} + +func newSmallHeuristic() func(n1, n2 graph.Node) float64 { + nds := []struct{ id, x, y int }{ + {1, 0, 6}, + {2, 1, 0}, + {3, 8, 7}, + {4, 16, 0}, + {5, 17, 6}, + {6, 9, 8}, + } + return func(n1, n2 graph.Node) float64 { + i1 := n1.ID() - 1 + i2 := n2.ID() - 1 + dx := nds[i2].x - nds[i1].x + dy := nds[i2].y - nds[i1].y + return math.Hypot(float64(dx), float64(dy)) + } +} + +type costEdgeListGraph interface { + graph.Coster + graph.EdgeListGraph +} + +func monotonic(g costEdgeListGraph, heur func(n1, n2 graph.Node) float64) (bool, graph.Edge, graph.Node) { + for _, goal := range g.NodeList() { + for _, edge := range g.EdgeList() { + head := edge.Head() + tail := edge.Tail() + if heur(head, goal) > g.Cost(edge)+heur(tail, goal) { + return false, edge, goal + } + } + } + return true, nil, nil +} + +// Test for correct result on a small graph easily solvable by hand +func TestDijkstraSmall(t *testing.T) { + g := newSmallGonumGraph() + paths, lens := search.Dijkstra(concrete.Node(1), g, nil) + s := fmt.Sprintln(len(paths), len(lens)) + for i := 1; i <= 6; i++ { + s += fmt.Sprintln(paths[i], lens[i]) + } + if s != `6 6 +[1] 0 +[1 2] 7 +[1 3] 9 +[1 3 4] 20 +[1 3 6 5] 20 +[1 3 6] 11 +` { + t.Fatal(s) + } +} + +func TestIsPath(t *testing.T) { + dg := concrete.NewDirectedGraph() + if !search.IsPath(nil, dg) { + t.Error("IsPath returns false on nil path") + } + p := []graph.Node{concrete.Node(0)} + if search.IsPath(p, dg) { + t.Error("IsPath returns true on nonexistant node") + } + dg.AddNode(p[0]) + if !search.IsPath(p, dg) { + t.Error("IsPath returns false on single-length path with existing node") + } + p = append(p, concrete.Node(1)) + dg.AddNode(p[1]) + if search.IsPath(p, dg) { + t.Error("IsPath returns true on bad path of length 2") + } + dg.AddDirectedEdge(concrete.Edge{p[0], p[1]}, 1) + if !search.IsPath(p, dg) { + t.Error("IsPath returns false on correct path of length 2") + } + p[0], p[1] = p[1], p[0] + if search.IsPath(p, dg) { + t.Error("IsPath erroneously returns true for a reverse path") + } + p = []graph.Node{p[1], p[0], concrete.Node(2)} + dg.AddDirectedEdge(concrete.Edge{p[1], p[2]}, 1) + if !search.IsPath(p, dg) { + t.Error("IsPath does not find a correct path for path > 2 nodes") + } + ug := concrete.NewGraph() + ug.AddUndirectedEdge(concrete.Edge{p[1], p[0]}, 1) + ug.AddUndirectedEdge(concrete.Edge{p[1], p[2]}, 1) + if !search.IsPath(p, ug) { + t.Error("IsPath does not correctly account for undirected behavior") + } +} + +type interval struct{ start, end int } + +var tarjanTests = []struct { + g []set + + ambiguousOrder []interval + want [][]int +}{ + + { + g: []set{ + 0: linksTo(1), + 1: linksTo(2, 7), + 2: linksTo(3, 6), + 3: linksTo(4), + 4: linksTo(2, 5), + 6: linksTo(3, 5), + 7: linksTo(0, 6), + }, + + want: [][]int{ + {5}, + {2, 3, 4, 6}, + {0, 1, 7}, + }, + }, + { + g: []set{ + 0: linksTo(1, 2, 3), + 1: linksTo(2), + 2: linksTo(3), + 3: linksTo(1), + }, + + want: [][]int{ + {1, 2, 3}, + {0}, + }, + }, + { + g: []set{ + 0: linksTo(1), + 1: linksTo(0, 2), + 2: linksTo(1), + }, + + want: [][]int{ + {0, 1, 2}, + }, + }, + { + g: []set{ + 0: linksTo(1), + 1: linksTo(2, 3), + 2: linksTo(4, 5), + 3: linksTo(4, 5), + 4: linksTo(6), + 5: nil, + 6: nil, + }, + + // Node pairs (2, 3) and (4, 5) are not + // relatively orderable within each pair. + ambiguousOrder: []interval{ + {0, 3}, // This includes node 6 since it only needs to be before 4 in topo sort. + {3, 5}, + }, + want: [][]int{ + {6}, {5}, {4}, {3}, {2}, {1}, {0}, + }, + }, + { + g: []set{ + 0: linksTo(1), + 1: linksTo(2, 3, 4), + 2: linksTo(0, 3), + 3: linksTo(4), + 4: linksTo(3), + }, + + // SCCs are not relatively ordable. + ambiguousOrder: []interval{ + {0, 2}, + }, + want: [][]int{ + {3, 4}, {0, 1, 2}, + }, + }, +} + +func TestTarjan(t *testing.T) { + for i, test := range tarjanTests { + g := concrete.NewDirectedGraph() + for u, e := range test.g { + g.AddNode(concrete.Node(u)) + for v := range e { + if !g.NodeExists(concrete.Node(v)) { + g.AddNode(concrete.Node(v)) + } + g.AddDirectedEdge(concrete.Edge{H: concrete.Node(u), T: concrete.Node(v)}, 0) + } + } + gotSCCs := search.Tarjan(g) + // tarjan.strongconnect does range iteration over maps, + // so sort SCC members to ensure consistent ordering. + gotIDs := make([][]int, len(gotSCCs)) + for i, scc := range gotSCCs { + gotIDs[i] = make([]int, len(scc)) + for j, id := range scc { + gotIDs[i][j] = id.ID() + } + sort.Ints(gotIDs[i]) + } + for _, iv := range test.ambiguousOrder { + sort.Sort(byComponentLengthOrStart(test.want[iv.start:iv.end])) + sort.Sort(byComponentLengthOrStart(gotIDs[iv.start:iv.end])) + } + if !reflect.DeepEqual(gotIDs, test.want) { + t.Errorf("unexpected Tarjan scc result for %d:\n\tgot:%v\n\twant:%v", i, gotIDs, test.want) + } + } +} + +// set is an integer set. +type set map[int]struct{} + +func linksTo(i ...int) set { + if len(i) == 0 { + return nil + } + s := make(set) + for _, v := range i { + s[v] = struct{}{} + } + return s +} + +type byComponentLengthOrStart [][]int + +func (c byComponentLengthOrStart) Len() int { return len(c) } +func (c byComponentLengthOrStart) Less(i, j int) bool { + return len(c[i]) < len(c[j]) || (len(c[i]) == len(c[j]) && c[i][0] < c[j][0]) +} +func (c byComponentLengthOrStart) Swap(i, j int) { c[i], c[j] = c[j], c[i] } diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/set.go b/Godeps/_workspace/src/github.com/gonum/graph/search/set.go new file mode 100644 index 000000000000..9f6f47b94d5f --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/set.go @@ -0,0 +1,206 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +import ( + "unsafe" + + "github.com/gonum/graph" +) + +// A set is a set of integer identifiers. +type intSet map[int]struct{} + +// The simple accessor methods for Set are provided to allow ease of +// implementation change should the need arise. + +// add inserts an element into the set. +func (s intSet) add(e int) { + s[e] = struct{}{} +} + +// has reports the existence of the element in the set. +func (s intSet) has(e int) bool { + _, ok := s[e] + return ok +} + +// remove delete the specified element from the set. +func (s intSet) remove(e int) { + delete(s, e) +} + +// count reports the number of elements stored in the set. +func (s intSet) count() int { + return len(s) +} + +// same determines whether two sets are backed by the same store. In the +// current implementation using hash maps it makes use of the fact that +// hash maps (at least in the gc implementation) are passed as a pointer +// to a runtime Hmap struct. +// +// A map is not seen by the runtime as a pointer though, so we cannot +// directly compare the sets converted to unsafe.Pointer and need to take +// the sets' addressed and dereference them as pointers to some comparable +// type. +func same(s1, s2 Set) bool { + return *(*uintptr)(unsafe.Pointer(&s1)) == *(*uintptr)(unsafe.Pointer(&s2)) +} + +// A set is a set of nodes keyed in their integer identifiers. +type Set map[int]graph.Node + +// The simple accessor methods for Set are provided to allow ease of +// implementation change should the need arise. + +// add inserts an element into the set. +func (s Set) add(n graph.Node) { + s[n.ID()] = n +} + +// has reports the existence of the element in the set. +func (s Set) has(n graph.Node) bool { + _, ok := s[n.ID()] + return ok +} + +// clear returns an empty set, possibly using the same backing store. +// clear is not provided as a method since there is no way to replace +// the calling value if clearing is performed by a make(set). clear +// should never be called without keeping the returned value. +func clear(s Set) Set { + if len(s) == 0 { + return s + } + + return make(Set) +} + +// copy performs a perfect copy from s1 to dst (meaning the sets will +// be equal). +func (dst Set) copy(src Set) Set { + if same(src, dst) { + return dst + } + + if len(dst) > 0 { + dst = make(Set, len(src)) + } + + for e, n := range src { + dst[e] = n + } + + return dst +} + +// equal reports set equality between the parameters. Sets are equal if +// and only if they have the same elements. +func equal(s1, s2 Set) bool { + if same(s1, s2) { + return true + } + + if len(s1) != len(s2) { + return false + } + + for e := range s1 { + if _, ok := s2[e]; !ok { + return false + } + } + + return true +} + +// union takes the union of s1 and s2, and stores it in dst. +// +// The union of two sets, s1 and s2, is the set containing all the +// elements of each, for instance: +// +// {a,b,c} UNION {d,e,f} = {a,b,c,d,e,f} +// +// Since sets may not have repetition, unions of two sets that overlap +// do not contain repeat elements, that is: +// +// {a,b,c} UNION {b,c,d} = {a,b,c,d} +// +func (dst Set) union(s1, s2 Set) Set { + if same(s1, s2) { + return dst.copy(s1) + } + + if !same(s1, dst) && !same(s2, dst) { + dst = clear(dst) + } + + if !same(dst, s1) { + for e, n := range s1 { + dst[e] = n + } + } + + if !same(dst, s2) { + for e, n := range s2 { + dst[e] = n + } + } + + return dst +} + +// intersect takes the intersection of s1 and s2, and stores it in dst. +// +// The intersection of two sets, s1 and s2, is the set containing all +// the elements shared between the two sets, for instance: +// +// {a,b,c} INTERSECT {b,c,d} = {b,c} +// +// The intersection between a set and itself is itself, and thus +// effectively a copy operation: +// +// {a,b,c} INTERSECT {a,b,c} = {a,b,c} +// +// The intersection between two sets that share no elements is the empty +// set: +// +// {a,b,c} INTERSECT {d,e,f} = {} +// +func (dst Set) intersect(s1, s2 Set) Set { + var swap Set + + if same(s1, s2) { + return dst.copy(s1) + } + if same(s1, dst) { + swap = s2 + } else if same(s2, dst) { + swap = s1 + } else { + dst = clear(dst) + + if len(s1) > len(s2) { + s1, s2 = s2, s1 + } + + for e, n := range s1 { + if _, ok := s2[e]; ok { + dst[e] = n + } + } + + return dst + } + + for e := range dst { + if _, ok := swap[e]; !ok { + delete(dst, e) + } + } + + return dst +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/search/set_test.go b/Godeps/_workspace/src/github.com/gonum/graph/search/set_test.go new file mode 100644 index 000000000000..118326fad769 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/search/set_test.go @@ -0,0 +1,422 @@ +// Copyright ©2014 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +import ( + "testing" + + "github.com/gonum/graph" +) + +type node int + +func (n node) ID() int { return int(n) } + +// count reports the number of elements stored in the set. +func (s Set) count() int { + return len(s) +} + +// remove delete the specified element from the set. +func (s Set) remove(n graph.Node) { + delete(s, n.ID()) +} + +// TestSame tests the assumption that pointer equality via unsafe conversion +// of a map[int]struct{} to uintptr is a valid test for perfect identity between +// set values. If any of the tests in TestSame fail, the package is broken and same +// must be reimplemented to conform to the runtime map implementation. The relevant +// code to look at (at least for gc) is in runtime/hashmap.{h,goc}. +func TestSame(t *testing.T) { + var ( + a = make(Set) + b = make(Set) + c = a + ) + + if same(a, b) { + t.Error("Independently created sets test as same") + } + if !same(a, c) { + t.Error("Set copy and original test as not same.") + } + a.add(node(1)) + if !same(a, c) { + t.Error("Set copy and original test as not same after addition.") + } + if !same(nil, nil) { + t.Error("nil sets test as not same.") + } + if same(b, nil) { + t.Error("nil and empty sets test as same.") + } +} + +func TestAdd(t *testing.T) { + s := make(Set) + if s == nil { + t.Fatal("Set cannot be created successfully") + } + + if s.count() != 0 { + t.Error("Set somehow contains new elements upon creation") + } + + s.add(node(1)) + s.add(node(3)) + s.add(node(5)) + + if s.count() != 3 { + t.Error("Incorrect number of set elements after adding") + } + + if !s.has(node(1)) || !s.has(node(3)) || !s.has(node(5)) { + t.Error("Set doesn't contain element that was added") + } + + s.add(node(1)) + + if s.count() > 3 { + t.Error("Set double-adds element (element not unique)") + } else if s.count() < 3 { + t.Error("Set double-add lowered len") + } + + if !s.has(node(1)) { + t.Error("Set doesn't contain double-added element") + } + + if !s.has(node(3)) || !s.has(node(5)) { + t.Error("Set removes element on double-add") + } + + for e, n := range s { + if e != n.ID() { + t.Error("Element ID did not match key: %d != %d", e, n.ID()) + } + } +} + +func TestRemove(t *testing.T) { + s := make(Set) + + s.add(node(1)) + s.add(node(3)) + s.add(node(5)) + + s.remove(node(1)) + + if s.count() != 2 { + t.Error("Incorrect number of set elements after removing an element") + } + + if s.has(node(1)) { + t.Error("Element present after removal") + } + + if !s.has(node(3)) || !s.has(node(5)) { + t.Error("Set remove removed wrong element") + } + + s.remove(node(1)) + + if s.count() != 2 || s.has(node(1)) { + t.Error("Double set remove does something strange") + } + + s.add(node(1)) + + if s.count() != 3 || !s.has(node(1)) { + t.Error("Cannot add element after removal") + } +} + +func TestClear(t *testing.T) { + s := make(Set) + + s.add(node(8)) + s.add(node(9)) + s.add(node(10)) + + s = clear(s) + + if s.count() != 0 { + t.Error("Clear did not properly reset set to size 0") + } +} + +func TestSelfEqual(t *testing.T) { + s := make(Set) + + if !equal(s, s) { + t.Error("Set is not equal to itself") + } + + s.add(node(1)) + + if !equal(s, s) { + t.Error("Set ceases self equality after adding element") + } +} + +func TestEqual(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + + if !equal(s1, s2) { + t.Error("Two different empty sets not equal") + } + + s1.add(node(1)) + if equal(s1, s2) { + t.Error("Two different sets with different elements not equal") + } + + s2.add(node(1)) + if !equal(s1, s2) { + t.Error("Two sets with same element not equal") + } +} + +func TestCopy(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + + s1.add(node(1)) + s1.add(node(2)) + s1.add(node(3)) + + s2.copy(s1) + + if !equal(s1, s2) { + t.Fatalf("Two sets not equal after copy") + } + + s2.remove(node(1)) + + if equal(s1, s2) { + t.Errorf("Mutating one set mutated another after copy") + } +} + +func TestSelfCopy(t *testing.T) { + s1 := make(Set) + + s1.add(node(1)) + s1.add(node(2)) + + s1.copy(s1) + + if s1.count() != 2 { + t.Error("Something strange happened when copying into self") + } +} + +func TestUnionSame(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + s3 := make(Set) + + s1.add(node(1)) + s1.add(node(2)) + + s2.add(node(1)) + s2.add(node(2)) + + s3.union(s1, s2) + + if s3.count() != 2 { + t.Error("Union of same sets yields set with wrong len") + } + + if !s3.has(node(1)) || !s3.has(node(2)) { + t.Error("Union of same sets yields wrong elements") + } + + for i, s := range []Set{s1, s2, s3} { + for e, n := range s { + if e != n.ID() { + t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) + } + } + } +} + +func TestUnionDiff(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + s3 := make(Set) + + s1.add(node(1)) + s1.add(node(2)) + + s2.add(node(3)) + + s3.union(s1, s2) + + if s3.count() != 3 { + t.Error("Union of different sets yields set with wrong len") + } + + if !s3.has(node(1)) || !s3.has(node(2)) || !s3.has(node(3)) { + t.Error("Union of different sets yields set with wrong elements") + } + + if s1.has(node(3)) || !s1.has(node(2)) || !s1.has(node(1)) || s1.count() != 2 { + t.Error("Union of sets mutates non-destination set (argument 1)") + } + + if !s2.has(node(3)) || s2.has(node(1)) || s2.has(node(2)) || s2.count() != 1 { + t.Error("Union of sets mutates non-destination set (argument 2)") + } + + for i, s := range []Set{s1, s2, s3} { + for e, n := range s { + if e != n.ID() { + t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) + } + } + } +} + +func TestUnionOverlapping(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + s3 := make(Set) + + s1.add(node(1)) + s1.add(node(2)) + + s2.add(node(2)) + s2.add(node(3)) + + s3.union(s1, s2) + + if s3.count() != 3 { + t.Error("Union of overlapping sets yields set with wrong len") + } + + if !s3.has(node(1)) || !s3.has(node(2)) || !s3.has(node(3)) { + t.Error("Union of overlapping sets yields set with wrong elements") + } + + if s1.has(node(3)) || !s1.has(node(2)) || !s1.has(node(1)) || s1.count() != 2 { + t.Error("Union of sets mutates non-destination set (argument 1)") + } + + if !s2.has(node(3)) || s2.has(node(1)) || !s2.has(node(2)) || s2.count() != 2 { + t.Error("Union of sets mutates non-destination set (argument 2)") + } + + for i, s := range []Set{s1, s2, s3} { + for e, n := range s { + if e != n.ID() { + t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) + } + } + } +} + +func TestIntersectSame(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + s3 := make(Set) + + s1.add(node(2)) + s1.add(node(3)) + + s2.add(node(2)) + s2.add(node(3)) + + s3.intersect(s1, s2) + + if card := s3.count(); card != 2 { + t.Errorf("Intersection of identical sets yields set of wrong len %d", card) + } + + if !s3.has(node(2)) || !s3.has(node(3)) { + t.Error("Intersection of identical sets yields set of wrong elements") + } + + for i, s := range []Set{s1, s2, s3} { + for e, n := range s { + if e != n.ID() { + t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) + } + } + } +} + +func TestIntersectDiff(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + s3 := make(Set) + + s1.add(node(2)) + s1.add(node(3)) + + s2.add(node(1)) + s2.add(node(4)) + + s3.intersect(s1, s2) + + if card := s3.count(); card != 0 { + t.Errorf("Intersection of different yields non-empty set %d", card) + } + + if !s1.has(node(2)) || !s1.has(node(3)) || s1.has(node(1)) || s1.has(node(4)) || s1.count() != 2 { + t.Error("Intersection of sets mutates non-destination set (argument 1)") + } + + if s2.has(node(2)) || s2.has(node(3)) || !s2.has(node(1)) || !s2.has(node(4)) || s2.count() != 2 { + t.Error("Intersection of sets mutates non-destination set (argument 1)") + } + + for i, s := range []Set{s1, s2, s3} { + for e, n := range s { + if e != n.ID() { + t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) + } + } + } +} + +func TestIntersectOverlapping(t *testing.T) { + s1 := make(Set) + s2 := make(Set) + s3 := make(Set) + + s1.add(node(2)) + s1.add(node(3)) + + s2.add(node(3)) + s2.add(node(4)) + + s3.intersect(s1, s2) + + if card := s3.count(); card != 1 { + t.Errorf("Intersection of overlapping sets yields set of incorrect len %d", card) + } + + if !s3.has(node(3)) { + t.Errorf("Intersection of overlapping sets yields set with wrong element") + } + + if !s1.has(node(2)) || !s1.has(node(3)) || s1.has(node(4)) || s1.count() != 2 { + t.Error("Intersection of sets mutates non-destination set (argument 1)") + } + + if s2.has(node(2)) || !s2.has(node(3)) || !s2.has(node(4)) || s2.count() != 2 { + t.Error("Intersection of sets mutates non-destination set (argument 1)") + } + + for i, s := range []Set{s1, s2, s3} { + for e, n := range s { + if e != n.ID() { + t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/gonum/graph/test-coverage.sh b/Godeps/_workspace/src/github.com/gonum/graph/test-coverage.sh new file mode 100644 index 000000000000..3df03f526a54 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gonum/graph/test-coverage.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# based on http://stackoverflow.com/questions/21126011/is-it-possible-to-post-coverage-for-multiple-packages-to-coveralls +# with script found at https://github.com/gopns/gopns/blob/master/test-coverage.sh + +echo "mode: set" > acc.out +returnval=`go test -v -coverprofile=profile.out` +echo ${returnval} +if [[ ${returnval} != *FAIL* ]] +then + if [ -f profile.out ] + then + cat profile.out | grep -v "mode: set" >> acc.out + fi +else + exit 1 +fi + +for Dir in $(find ./* -maxdepth 10 -type d ); +do + if ls $Dir/*.go &> /dev/null; + then + echo $Dir + returnval=`go test -v -coverprofile=profile.out $Dir` + echo ${returnval} + if [[ ${returnval} != *FAIL* ]] + then + if [ -f profile.out ] + then + cat profile.out | grep -v "mode: set" >> acc.out + fi + else + exit 1 + fi + fi +done +if [ -n "$COVERALLS_TOKEN" ] +then + $HOME/gopath/bin/goveralls -coverprofile=acc.out -service=travis-ci -repotoken $COVERALLS_TOKEN +fi + +rm -rf ./profile.out +rm -rf ./acc.out diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index ee23d15b93a6..b2291343ade5 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -357,6 +357,8 @@ echo "deploymentConfigs: ok" osc process -f test/templates/fixtures/guestbook.json --parameters --value="ADMIN_USERNAME=admin" osc process -f test/templates/fixtures/guestbook.json | osc create -f - +osc status +[ "$(osc status | grep frontend-service)" ] echo "template+config: ok" openshift kube resize --replicas=2 rc guestbook @@ -439,3 +441,9 @@ echo "ex registry: ok" echo "ex build-chain: ok" osc get minions,pods + +osadm new-project example --admin="createuser" +osc project example +osc create -f test/fixtures/app-scenarios +osc status +echo "complex-scenarios: ok" \ No newline at end of file diff --git a/pkg/api/graph/deployment.go b/pkg/api/graph/deployment.go new file mode 100644 index 000000000000..03eac8484203 --- /dev/null +++ b/pkg/api/graph/deployment.go @@ -0,0 +1,419 @@ +package graph + +import ( + "sort" + + "github.com/gonum/graph" + "github.com/gonum/graph/search" + + kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + + //build "github.com/openshift/origin/pkg/build/api" + deploy "github.com/openshift/origin/pkg/deploy/api" + image "github.com/openshift/origin/pkg/image/api" +) + +// DeploymentPipelineMap describes a single deployment config and the objects +// that contributed to that deployment. +type DeploymentPipelineMap map[*DeploymentConfigNode][]ImagePipeline + +// ImagePipeline represents a build, its output, and any inputs. The input +// to a build may be another ImagePipeline. +type ImagePipeline struct { + Image ImageTagLocation + Build *BuildConfigNode + // If set, the base image used by the build + BaseImage ImageTagLocation + // If set, the source repository that inputs to the build + Source SourceLocation +} + +type DeploymentFlow struct { + Deployment *DeploymentConfigNode + Images []ImagePipeline +} + +// ImageTagLocation identifies the source or destination of an image. Represents +// both a tag in a Docker image repository, as well as a tag in an OpenShift image stream. +type ImageTagLocation interface { + ID() int + ImageSpec() string + ImageTag() string +} + +// SourceLocation identifies a repository that is an input to a build. +type SourceLocation interface { + ID() int +} + +// DeploymentPipelines returns a map of DeploymentConfigs to the deployment flows that create them, +// extracted from the provided Graph. +func DeploymentPipelines(g Graph) (DeploymentPipelineMap, NodeSet) { + covered := make(NodeSet) + g = g.EdgeSubgraph(ReverseGraphEdge) + flows := make(DeploymentPipelineMap) + for _, node := range g.NodeList() { + switch t := node.(type) { + case *DeploymentConfigNode: + covered.Add(t.ID()) + images := []ImagePipeline{} + for _, n := range g.Neighbors(node) { + // find incoming image edges only + switch g.EdgeKind(g.EdgeBetween(node, n)) { + case TriggersDeploymentGraphEdgeKind, UsedInDeploymentGraphEdgeKind: + if flow, ok := ImagePipelineFromNode(g, n, covered); ok { + images = append(images, flow) + } + } + } + + output := []ImagePipeline{} + + // ensure the list of images is ordered the same as what is in the template + if template := t.DeploymentConfig.Template.ControllerTemplate.Template; template != nil { + EachTemplateImage( + &template.Spec, + DeploymentConfigHasTrigger(t.DeploymentConfig), + func(image TemplateImage, err error) { + if err != nil { + return + } + for i := range images { + switch t := images[i].Image.(type) { + case *ImageStreamTagNode: + if image.Ref != nil { + continue + } + from := image.From + if t.ImageStream.Name != from.Name || t.ImageStream.Namespace != from.Namespace { + continue + } + output = append(output, images[i]) + return + case *DockerImageRepositoryNode: + if image.From != nil { + continue + } + ref1, ref2 := t.Ref.Minimal(), image.Ref.DockerClientDefaults().Minimal() + if ref1 != ref2 { + continue + } + output = append(output, images[i]) + return + } + } + }, + ) + } + flows[t] = output + } + } + return flows, covered +} + +// ImagePipelineFromNode attempts to locate a build flow from the provided node. If no such +// build flow can be located, false is returned. +func ImagePipelineFromNode(g Graph, n graph.Node, covered NodeSet) (ImagePipeline, bool) { + flow := ImagePipeline{} + switch node := n.(type) { + + case *BuildConfigNode: + covered.Add(n.ID()) + base, src, _ := findBuildInputs(g, n, covered) + flow.Build = node + flow.BaseImage = base + flow.Source = src + return flow, true + + case ImageTagLocation: + covered.Add(n.ID()) + flow.Image = node + for _, input := range g.Neighbors(n) { + switch g.EdgeKind(g.EdgeBetween(n, input)) { + case BuildOutputGraphEdgeKind: + covered.Add(input.ID()) + build := input.(*BuildConfigNode) + if flow.Build != nil { + // report this as an error (unexpected duplicate input build) + } + if build.BuildConfig == nil { + // report this as as a missing build / broken link + break + } + base, src, _ := findBuildInputs(g, input, covered) + flow.Build = build + flow.BaseImage = base + flow.Source = src + } + } + return flow, true + + default: + return flow, false + } +} + +func findBuildInputs(g Graph, n graph.Node, covered NodeSet) (base ImageTagLocation, source SourceLocation, err error) { + // find inputs to the build + for _, input := range g.Neighbors(n) { + switch g.EdgeKind(g.EdgeBetween(n, input)) { + case BuildInputGraphEdgeKind: + if source != nil { + // report this as an error (unexpected duplicate source) + } + covered.Add(input.ID()) + source = input.(SourceLocation) + case BuildInputImageGraphEdgeKind: + if base != nil { + // report this as an error (unexpected duplicate input build) + } + covered.Add(input.ID()) + base = input.(ImageTagLocation) + } + } + return +} + +// ServiceAndDeploymentGroups breaks the provided graph of API relationships into ServiceGroup objects, +// ordered consistently. Groups are organized so that overlapping Services and DeploymentConfigs are +// part of the same group, Deployment Configs are each in their own group, and then BuildConfigs are +// part of the last service group. +func ServiceAndDeploymentGroups(g Graph) []ServiceGroup { + deploys, covered := DeploymentPipelines(g) + other := g.Subgraph(UncoveredDeploymentFlowNodes(covered), UncoveredDeploymentFlowEdges(covered)) + components := search.Tarjan(other) + + serviceGroups := []ServiceGroup{} + for _, c := range components { + group := ServiceGroup{} + + matches := NodesByKind(other, c, ServiceGraphKind, DeploymentConfigGraphKind) + svcs, dcs, _ := matches[0], matches[1], matches[2] + + for _, n := range svcs { + covers := []*DeploymentConfigNode{} + for _, neighbor := range other.Neighbors(n) { + switch other.EdgeKind(g.EdgeBetween(neighbor, n)) { + case ExposedThroughServiceGraphEdgeKind: + covers = append(covers, neighbor.(*DeploymentConfigNode)) + } + } + group.Services = append(group.Services, ServiceReference{ + Service: n.(*ServiceNode), + Covers: covers, + }) + } + sort.Sort(SortedServiceReferences(group.Services)) + + for _, n := range dcs { + d := n.(*DeploymentConfigNode) + group.Deployments = append(group.Deployments, DeploymentFlow{ + Deployment: d, + Images: deploys[d], + }) + } + sort.Sort(SortedDeploymentPipelines(group.Deployments)) + + if len(dcs) == 0 || len(svcs) == 0 { + unknown := g.SubgraphWithNodes(c, ExistingDirectEdge) + for _, n := range unknown.NodeList() { + g.PredecessorEdges(n, AddGraphEdgesTo(unknown), BuildOutputGraphEdgeKind) + } + unknown = unknown.EdgeSubgraph(ReverseGraphEdge) + for _, n := range unknown.RootNodes() { + if flow, ok := ImagePipelineFromNode(unknown, n, make(NodeSet)); ok { + group.Builds = append(group.Builds, flow) + } + } + } + sort.Sort(SortedImagePipelines(group.Builds)) + + serviceGroups = append(serviceGroups, group) + } + sort.Sort(SortedServiceGroups(serviceGroups)) + return serviceGroups +} + +// UncoveredDeploymentFlowEdges preserves (and duplicates) edges that were not +// covered by a deployment flow. As a special case, it preserves edges between +// Services and DeploymentConfigs. +func UncoveredDeploymentFlowEdges(covered NodeSet) EdgeFunc { + return func(g Interface, head, tail graph.Node, edgeKind int) bool { + if edgeKind == ExposedThroughServiceGraphEdgeKind { + return AddReversedEdge(g, head, tail, ReferencedByGraphEdgeKind) + } + if covered.Has(head.ID()) && covered.Has(tail.ID()) { + return false + } + return AddReversedEdge(g, head, tail, ReferencedByGraphEdgeKind) + } +} + +// UncoveredDeploymentFlowNodes includes nodes that either services or deployment +// configs, or which haven't previously been covered. +func UncoveredDeploymentFlowNodes(covered NodeSet) NodeFunc { + return func(g Interface, node graph.Node) bool { + switch node.(type) { + case *DeploymentConfigNode, *ServiceNode: + return true + } + return !covered.Has(node.ID()) + } +} + +// ServiceReference is a service and the DeploymentConfigs it covers +type ServiceReference struct { + Service *ServiceNode + Covers []*DeploymentConfigNode +} + +// ServiceGroup is a related set of resources that should be displayed together +// logically. They are usually sorted internally. +type ServiceGroup struct { + Services []ServiceReference + Deployments []DeploymentFlow + Builds []ImagePipeline +} + +// Sorts on the provided objects. + +type SortedServiceReferences []ServiceReference + +func (m SortedServiceReferences) Len() int { return len(m) } +func (m SortedServiceReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] } +func (m SortedServiceReferences) Less(i, j int) bool { + return CompareObjectMeta(&m[i].Service.ObjectMeta, &m[j].Service.ObjectMeta) +} + +type SortedDeploymentPipelines []DeploymentFlow + +func (m SortedDeploymentPipelines) Len() int { return len(m) } +func (m SortedDeploymentPipelines) Swap(i, j int) { m[i], m[j] = m[j], m[i] } +func (m SortedDeploymentPipelines) Less(i, j int) bool { + return CompareObjectMeta(&m[i].Deployment.ObjectMeta, &m[j].Deployment.ObjectMeta) +} + +type SortedImagePipelines []ImagePipeline + +func (m SortedImagePipelines) Len() int { return len(m) } +func (m SortedImagePipelines) Swap(i, j int) { m[i], m[j] = m[j], m[i] } +func (m SortedImagePipelines) Less(i, j int) bool { + return CompareImagePipeline(&m[i], &m[j]) +} + +type SortedServiceGroups []ServiceGroup + +func (m SortedServiceGroups) Len() int { return len(m) } +func (m SortedServiceGroups) Swap(i, j int) { m[i], m[j] = m[j], m[i] } +func (m SortedServiceGroups) Less(i, j int) bool { + a, b := m[i], m[j] + switch { + case len(a.Services) != 0 && len(b.Services) != 0: + return CompareObjectMeta(&a.Services[0].Service.ObjectMeta, &b.Services[0].Service.ObjectMeta) + case len(a.Services) != 0: + return true + case len(b.Services) != 0: + return false + } + switch { + case len(a.Deployments) != 0 && len(b.Deployments) != 0: + return CompareObjectMeta(&a.Deployments[0].Deployment.ObjectMeta, &b.Deployments[0].Deployment.ObjectMeta) + case len(a.Deployments) != 0: + return true + case len(b.Deployments) != 0: + return false + } + switch { + case len(a.Builds) != 0 && len(b.Builds) != 0: + return CompareImagePipeline(&a.Builds[0], &b.Builds[0]) + case len(a.Deployments) != 0: + return true + case len(b.Deployments) != 0: + return false + } + return true +} + +func CompareObjectMeta(a, b *kapi.ObjectMeta) bool { + if a.Namespace == b.Namespace { + return a.Name < b.Name + } + return a.Namespace < b.Namespace +} + +func CompareImagePipeline(a, b *ImagePipeline) bool { + switch { + case a.Build != nil && b.Build != nil: + return CompareObjectMeta(&a.Build.ObjectMeta, &b.Build.ObjectMeta) + case a.Build != nil: + return true + case b.Build != nil: + return false + } + if a.Image == nil || b.Image == nil { + return true + } + return a.Image.ImageSpec() < b.Image.ImageSpec() +} + +// TODO: move to deploy/api/helpers.go + +type TemplateImage struct { + Image string + + Ref *image.DockerImageReference + + From *kapi.ObjectReference + FromTag string +} + +type TriggeredByFunc func(container *kapi.Container) (TemplateImage, bool) + +func EachTemplateImage(pod *kapi.PodSpec, triggerFn TriggeredByFunc, fn func(TemplateImage, error)) { + for _, container := range pod.Containers { + var ref image.DockerImageReference + if trigger, ok := triggerFn(&container); ok { + trigger.Image = container.Image + fn(trigger, nil) + continue + } + ref, err := image.ParseDockerImageReference(container.Image) + if err != nil { + fn(TemplateImage{Image: container.Image}, err) + continue + } + fn(TemplateImage{Image: container.Image, Ref: &ref}, nil) + } +} + +func DeploymentConfigHasTrigger(config *deploy.DeploymentConfig) TriggeredByFunc { + return func(container *kapi.Container) (TemplateImage, bool) { + for _, trigger := range config.Triggers { + params := trigger.ImageChangeParams + if params == nil { + continue + } + for _, name := range params.ContainerNames { + if container.Name == name { + if len(params.From.Name) == 0 { + continue + } + tag := params.Tag + if len(tag) == 0 { + tag = "latest" + } + from := params.From + if len(from.Namespace) == 0 { + from.Namespace = config.Namespace + } + return TemplateImage{ + Image: container.Image, + From: &from, + FromTag: tag, + }, true + } + } + } + return TemplateImage{}, false + } +} diff --git a/pkg/api/graph/graph.go b/pkg/api/graph/graph.go new file mode 100644 index 000000000000..008ae56e470e --- /dev/null +++ b/pkg/api/graph/graph.go @@ -0,0 +1,402 @@ +package graph + +import ( + "fmt" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" +) + +type Node struct { + concrete.Node + UniqueName +} + +type UniqueName string + +func (n UniqueName) UniqueName() string { + return string(n) +} + +type uniqueNamer interface { + UniqueName() string +} + +// UniqueNodeInitializer is a graph that allows nodes with a unique name to be added without duplication. +// If the node is newly added, true will be returned. +type UniqueNodeInitializer interface { + FindOrCreate(name UniqueName, fn NodeInitializerFunc) (graph.Node, bool) +} + +type NodeInitializerFunc func(Node) graph.Node + +func EnsureUnique(g UniqueNodeInitializer, name UniqueName, fn NodeInitializerFunc) graph.Node { + node, _ := g.FindOrCreate(name, fn) + return node +} + +type MutableDirectedEdge interface { + AddEdge(head, tail graph.Node, edgeKind int) +} + +type MutableUniqueGraph interface { + graph.Mutable + MutableDirectedEdge + UniqueNodeInitializer +} + +type Edge struct { + concrete.Edge + K int +} + +func NewEdge(head, tail graph.Node, kind int) Edge { + return Edge{concrete.Edge{head, tail}, kind} +} + +func (e Edge) Kind() int { + return e.K +} + +type GraphDescriber interface { + Name(node graph.Node) string + Kind(node graph.Node) int + Object(node graph.Node) interface{} + EdgeKind(edge graph.Edge) int +} + +type Interface interface { + graph.DirectedGraph + graph.EdgeLister + + GraphDescriber + MutableUniqueGraph +} + +type Graph struct { + // the standard graph + graph.DirectedGraph + // helper methods for switching on the kind and types of the node + GraphDescriber + + // exposes the public interface for adding nodes + uniqueNamedGraph + // the internal graph object, which allows edges and nodes to be directly added + internal *concrete.DirectedGraph +} + +// Graph must implement MutableUniqueGraph +var _ MutableUniqueGraph = Graph{} + +// New initializes a graph from input to output. +func New() Graph { + g := concrete.NewDirectedGraph() + return Graph{ + DirectedGraph: g, + GraphDescriber: typedGraph{}, + + uniqueNamedGraph: newUniqueNamedGraph(g), + + internal: g, + } +} + +// RootNodes returns all the roots of this graph. +func (g Graph) RootNodes() []graph.Node { + roots := []graph.Node{} + for _, n := range g.internal.NodeList() { + if len(g.internal.Predecessors(n)) != 0 { + continue + } + roots = append(roots, n) + } + return roots +} + +// PredecessorEdges invokes fn with all of the predecessor edges of node that have the specified +// edge kind. +func (g Graph) PredecessorEdges(node graph.Node, fn EdgeFunc, edgeKind ...int) { + for _, n := range g.Predecessors(node) { + edge := g.EdgeBetween(n, node) + kind := g.EdgeKind(edge) + for _, allowed := range edgeKind { + if allowed != kind { + continue + } + fn(g, n, node, kind) + break + } + } +} + +// SuccessorEdges invokes fn with all of the successor edges of node that have the specified +// edge kind. +func (g Graph) SuccessorEdges(node graph.Node, fn EdgeFunc, edgeKind ...int) { + for _, n := range g.Successors(node) { + edge := g.EdgeBetween(node, n) + kind := g.EdgeKind(edge) + for _, allowed := range edgeKind { + if allowed != kind { + continue + } + fn(g, node, n, kind) + break + } + } +} + +func (g Graph) EdgeList() []graph.Edge { + return g.internal.EdgeList() +} + +func (g Graph) AddNode(n graph.Node) { + g.internal.AddNode(n) +} + +// AddEdge implements MutableUniqueGraph +func (g Graph) AddEdge(head, tail graph.Node, edgeKind int) { + g.internal.AddDirectedEdge(NewEdge(head, tail, edgeKind), 1) +} + +// addEdges adds the specified edges, filtered by the provided edge connection +// function. +func (g Graph) addEdges(edges []graph.Edge, fn EdgeFunc) { + for _, e := range edges { + switch t := e.(type) { + case concrete.WeightedEdge: + if fn(g, t.Head(), t.Tail(), t.Edge.(Edge).K) { + g.internal.AddDirectedEdge(t.Edge.(Edge), t.Cost) + } + case Edge: + if fn(g, t.Head(), t.Tail(), t.K) { + g.internal.AddDirectedEdge(t, 1.0) + } + default: + panic("bad edge") + } + } +} + +// NodeFunc is passed a new graph, a node in the graph, and should return true if the +// node should be included. +type NodeFunc func(g Interface, n graph.Node) bool + +// EdgeFunc is passed a new graph, an edge in the current graph, and should mutate +// the new graph as needed. If true is returned, the existing edge will be added to the graph. +type EdgeFunc func(g Interface, head, tail graph.Node, edgeKind int) bool + +// Subgraph returns the directed subgraph with only the nodes and edges that match the +// provided functions. +func (g Graph) EdgeSubgraph(edgeFn EdgeFunc) Graph { + out := New() + for _, node := range g.NodeList() { + out.internal.AddNode(node) + } + out.addEdges(g.internal.EdgeList(), edgeFn) + return out +} + +// Subgraph returns the directed subgraph with only the nodes and edges that match the +// provided functions. +func (g Graph) Subgraph(nodeFn NodeFunc, edgeFn EdgeFunc) Graph { + out := New() + for _, node := range g.NodeList() { + if nodeFn(out, node) { + out.internal.AddNode(node) + } + } + out.addEdges(g.internal.EdgeList(), edgeFn) + return out +} + +// SubgraphWithNodes returns the directed subgraph with only the listed nodes and edges that +// match the provided function. +func (g Graph) SubgraphWithNodes(nodes []graph.Node, fn EdgeFunc) Graph { + out := New() + for _, node := range nodes { + out.internal.AddNode(node) + } + out.addEdges(g.internal.EdgeList(), fn) + return out +} + +// ConnectedEdgeSubgraph creates a new graph that iterates through all edges in the graph +// and includes all edges the provided function returns true for. Nodes not referenced by +// an edge will be dropped unless the function adds them explicitly. +func (g Graph) ConnectedEdgeSubgraph(fn EdgeFunc) Graph { + out := New() + out.addEdges(g.internal.EdgeList(), fn) + return out +} + +// AllNodes includes all nodes in the graph +func AllNodes(g Interface, node graph.Node) bool { + return true +} + +// ExistingDirectEdge returns true if both head and tail already exist in the graph and the edge kind is +// not ReferencedByGraphEdgeKind (the generic reverse edge kind). This will purge the graph of any +// edges created by AddReversedEdge. +func ExistingDirectEdge(g Interface, head, tail graph.Node, edgeKind int) bool { + return edgeKind != ReferencedByGraphEdgeKind && g.NodeExists(head) && g.NodeExists(tail) +} + +// ExistingDirectEdge returns true if both nodes exist in the graph already and the edge kind is +// not ReferencedByGraphEdgeKind (the generic reverse edge kind). +func ReverseExistingDirectEdge(g Interface, head, tail graph.Node, edgeKind int) bool { + return ExistingDirectEdge(g, head, tail, edgeKind) && ReverseGraphEdge(g, head, tail, edgeKind) +} + +// ReverseGraphEdge reverses the order of the edge and drops the existing edge. +func ReverseGraphEdge(g Interface, head, tail graph.Node, edgeKind int) bool { + g.AddEdge(tail, head, edgeKind) + return false +} + +// AddReversedEdge adds a reversed edge for every passed edge and preserves the existing +// edge. Used to convert a one directional edge into a bidirectional edge, but will +// create duplicate edges if a bidirectional edge between two nodes already exists. +func AddReversedEdge(g Interface, head, tail graph.Node, edgeKind int) bool { + g.AddEdge(tail, head, ReferencedByGraphEdgeKind) + return true +} + +// AddGraphEdgesTo returns an EdgeFunc that will add the selected edges to the passed +// graph. +func AddGraphEdgesTo(g Interface) EdgeFunc { + return func(_ Interface, head, tail graph.Node, edgeKind int) bool { + g.AddEdge(head, tail, edgeKind) + return false + } +} + +type uniqueNamedGraph struct { + graph.Mutable + names map[UniqueName]graph.Node +} + +func newUniqueNamedGraph(g graph.Mutable) uniqueNamedGraph { + return uniqueNamedGraph{ + Mutable: g, + names: make(map[UniqueName]graph.Node), + } +} + +func (g uniqueNamedGraph) FindOrCreate(name UniqueName, fn NodeInitializerFunc) (graph.Node, bool) { + if node, ok := g.names[name]; ok { + return node, true + } + id := g.NewNode().ID() + node := fn(Node{concrete.Node(id), name}) + g.names[name] = node + g.AddNode(node) + return node, false +} + +type typedGraph struct{} + +type stringer interface { + String() string +} + +func (g typedGraph) Name(node graph.Node) string { + switch t := node.(type) { + case stringer: + return t.String() + case uniqueNamer: + return t.UniqueName() + default: + return fmt.Sprintf("", node.ID()) + } +} + +type objectifier interface { + Object() interface{} +} + +func (g typedGraph) Object(node graph.Node) interface{} { + switch t := node.(type) { + case objectifier: + return t.Object() + default: + return nil + } +} + +type kind interface { + Kind() int +} + +func (g typedGraph) Kind(node graph.Node) int { + if k, ok := node.(kind); ok { + return k.Kind() + } + return UnknownGraphKind +} + +func (g typedGraph) EdgeKind(edge graph.Edge) int { + var e Edge + switch t := edge.(type) { + case concrete.WeightedEdge: + e = t.Edge.(Edge) + case Edge: + e = t + default: + return UnknownGraphEdgeKind + } + return e.Kind() +} + +type NodeSet map[int]struct{} + +func (n NodeSet) Has(id int) bool { + _, ok := n[id] + return ok +} + +func (n NodeSet) Add(id int) { + n[id] = struct{}{} +} + +func NodesByKind(g Interface, nodes []graph.Node, kinds ...int) [][]graph.Node { + buckets := make(map[int]int) + for i, kind := range kinds { + buckets[kind] = i + } + if nodes == nil { + nodes = g.NodeList() + } + + last := len(kinds) + result := make([][]graph.Node, last+1) + for _, node := range nodes { + if bucket, ok := buckets[g.Kind(node)]; ok { + result[bucket] = append(result[bucket], node) + } else { + result[last] = append(result[last], node) + } + } + return result +} + +func pathCovered(path []graph.Node, paths map[int][]graph.Node) bool { + l := len(path) + for _, existing := range paths { + if l >= len(existing) { + continue + } + if pathEqual(path, existing) { + return true + } + } + return false +} + +func pathEqual(a, b []graph.Node) bool { + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/pkg/api/graph/graph_test.go b/pkg/api/graph/graph_test.go new file mode 100644 index 000000000000..c647040a2bf5 --- /dev/null +++ b/pkg/api/graph/graph_test.go @@ -0,0 +1,251 @@ +package graph + +import ( + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/concrete" + + kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + + build "github.com/openshift/origin/pkg/build/api" + deploy "github.com/openshift/origin/pkg/deploy/api" + image "github.com/openshift/origin/pkg/image/api" +) + +func TestGraph(t *testing.T) { + g := New() + BuildConfig(g, &build.BuildConfig{ + ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "build1"}, + Triggers: []build.BuildTriggerPolicy{ + { + ImageChange: &build.ImageChangeTrigger{ + From: kapi.ObjectReference{Name: "test"}, + Tag: "base-image", + }, + }, + { + ImageChange: &build.ImageChangeTrigger{ + From: kapi.ObjectReference{}, + }, + }, + }, + Parameters: build.BuildParameters{ + Output: build.BuildOutput{ + To: &kapi.ObjectReference{Name: "other"}, + Tag: "tag1", + }, + }, + }) + BuildConfig(g, &build.BuildConfig{ + ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "test"}, + Parameters: build.BuildParameters{ + Output: build.BuildOutput{ + To: &kapi.ObjectReference{Name: "other"}, + Tag: "base-image", + }, + }, + }) + BuildConfig(g, &build.BuildConfig{ + ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "build2"}, + Parameters: build.BuildParameters{ + Output: build.BuildOutput{ + DockerImageReference: "mycustom/repo/image", + Tag: "tag2", + }, + }, + }) + Service(g, &kapi.Service{ + ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc-is-ignored"}, + Spec: kapi.ServiceSpec{ + Selector: nil, + }, + }) + Service(g, &kapi.Service{ + ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc1"}, + Spec: kapi.ServiceSpec{ + Selector: map[string]string{ + "deploymentconfig": "deploy1", + }, + }, + }) + Service(g, &kapi.Service{ + ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc2"}, + Spec: kapi.ServiceSpec{ + Selector: map[string]string{ + "deploymentconfig": "deploy1", + "env": "prod", + }, + }, + }) + DeploymentConfig(g, &deploy.DeploymentConfig{ + ObjectMeta: kapi.ObjectMeta{Namespace: "other", Name: "deploy1"}, + Triggers: []deploy.DeploymentTriggerPolicy{ + { + ImageChangeParams: &deploy.DeploymentTriggerImageChangeParams{ + From: kapi.ObjectReference{Namespace: "default", Name: "other"}, + ContainerNames: []string{"1", "2"}, + Tag: "tag1", + }, + }, + }, + Template: deploy.DeploymentTemplate{ + ControllerTemplate: kapi.ReplicationControllerSpec{ + Template: &kapi.PodTemplateSpec{ + ObjectMeta: kapi.ObjectMeta{ + Labels: map[string]string{ + "deploymentconfig": "deploy1", + "env": "prod", + }, + }, + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + { + Name: "1", + Image: "mycustom/repo/image", + }, + { + Name: "2", + Image: "mycustom/repo/image2", + }, + { + Name: "3", + Image: "mycustom/repo/image3", + }, + }, + }, + }, + }, + }, + }) + DeploymentConfig(g, &deploy.DeploymentConfig{ + ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "deploy2"}, + Template: deploy.DeploymentTemplate{ + ControllerTemplate: kapi.ReplicationControllerSpec{ + Template: &kapi.PodTemplateSpec{ + ObjectMeta: kapi.ObjectMeta{ + Labels: map[string]string{ + "deploymentconfig": "deploy2", + "env": "dev", + }, + }, + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + { + Name: "1", + Image: "someother/image:v1", + }, + }, + }, + }, + }, + }, + }) + + CoverServices(g) + ir, dc, bc, other := 0, 0, 0, 0 + for _, node := range g.NodeList() { + t.Logf("node: %d %v", node.ID(), node) + switch g.Object(node).(type) { + case *deploy.DeploymentConfig: + if g.Kind(node) != DeploymentConfigGraphKind { + t.Fatalf("unexpected kind: %v", g.Kind(node)) + } + dc++ + case *build.BuildConfig: + if g.Kind(node) != BuildConfigGraphKind { + t.Fatalf("unexpected kind: %v", g.Kind(node)) + } + bc++ + case *image.ImageStream: + if g.Kind(node) != ImageStreamGraphKind { + t.Fatalf("unexpected kind: %v", g.Kind(node)) + } + ir++ + default: + other++ + } + } + if dc != 2 || bc != 3 || ir != 3 || other != 6 { + t.Errorf("unexpected nodes: %d %d %d %d", dc, bc, ir, other) + } + for _, edge := range g.internal.EdgeList() { + if g.EdgeKind(edge) == UnknownGraphEdgeKind { + t.Errorf("edge reported unknown kind: %#v", edge) + } + t.Logf("edge: %v", edge) + } + reverse := g.EdgeSubgraph(ReverseGraphEdge) + for _, edge := range reverse.internal.EdgeList() { + t.Logf("redge: %v", edge) + } + + edge := g.EdgeBetween(concrete.Node(4), concrete.Node(5)) + if len(g.SubgraphWithNodes([]graph.Node{edge.Head(), edge.Tail()}, ExistingDirectEdge).EdgeList()) != 1 { + t.Fatalf("expected one edge") + } + if len(g.SubgraphWithNodes([]graph.Node{edge.Tail(), edge.Head()}, ExistingDirectEdge).EdgeList()) != 1 { + t.Fatalf("expected one edge") + } + + if e := g.EdgeBetween(concrete.Node(4), concrete.Node(5)); e == nil { + t.Errorf("expected edge for 4-5") + } + if e := g.EdgeBetween(concrete.Node(5), concrete.Node(4)); e == nil { + t.Errorf("expected edge for 4-5") + } + + pipelines, covered := DeploymentPipelines(g) + if len(pipelines) != 2 { + t.Fatalf("unexpected pipelines: %#v", pipelines) + } + if len(covered) != 7 { + t.Fatalf("unexpected covered nodes: %#v", covered) + } + for from, images := range pipelines { + t.Logf("from %s", from.Name) + for _, path := range images { + t.Logf(" %v", path) + } + } + + serviceGroups := ServiceAndDeploymentGroups(g) + if len(serviceGroups) != 5 { + t.Errorf("unexpected service groups: %#v", serviceGroups) + } + if len(serviceGroups[3].Builds) != 1 { + t.Fatalf("unexpected final group: %#v", serviceGroups[2]) + } + for _, group := range serviceGroups { + dcs := len(group.Deployments) + svcs := len(group.Services) + for _, svc := range group.Services { + t.Logf("service %s", svc.Service.Name) + } + indent := "" + if svcs > 0 { + indent = " " + } + for _, deployment := range group.Deployments { + t.Logf("%sdeployment %s", indent, deployment.Deployment.Name) + for _, image := range deployment.Images { + t.Logf("%s image %s", indent, image.Image.ImageSpec()) + } + } + if dcs != 0 || svcs != 0 { + continue + } + for _, build := range group.Builds { + if build.Image != nil { + if build.Build != nil { + t.Logf("%s <- build %s (%d)", build.Image.ImageSpec(), build.Build.Name, build.Image.ID()) + } else { + t.Logf("%s (%d)", build.Image.ImageSpec(), build.Image.ID()) + } + } else { + t.Logf("build %s (%d)", build.Build.Name, build.Build.ID()) + t.Errorf("expected build %d to have an image edge", build.Build.ID()) + } + } + } +} diff --git a/pkg/api/graph/types.go b/pkg/api/graph/types.go new file mode 100644 index 000000000000..395ed64572ee --- /dev/null +++ b/pkg/api/graph/types.go @@ -0,0 +1,342 @@ +package graph + +import ( + "fmt" + + "github.com/gonum/graph" + + kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + + build "github.com/openshift/origin/pkg/build/api" + deploy "github.com/openshift/origin/pkg/deploy/api" + image "github.com/openshift/origin/pkg/image/api" +) + +const ( + UnknownGraphKind = iota + ImageStreamGraphKind + DockerRepositoryGraphKind + BuildConfigGraphKind + DeploymentConfigGraphKind + SourceRepositoryGraphKind + ServiceGraphKind +) +const ( + UnknownGraphEdgeKind = iota + ReferencedByGraphEdgeKind + BuildInputImageGraphEdgeKind + TriggersDeploymentGraphEdgeKind + BuildInputGraphEdgeKind + BuildOutputGraphEdgeKind + UsedInDeploymentGraphEdgeKind + ExposedThroughServiceGraphEdgeKind +) + +type ServiceNode struct { + Node + *kapi.Service +} + +func (n ServiceNode) Object() interface{} { + return n.Service +} + +func (n ServiceNode) String() string { + return fmt.Sprintf("", n.Namespace, n.Name) +} + +func (*ServiceNode) Kind() int { + return ServiceGraphKind +} + +type BuildConfigNode struct { + Node + *build.BuildConfig +} + +func (n BuildConfigNode) Object() interface{} { + return n.BuildConfig +} + +func (n BuildConfigNode) String() string { + return fmt.Sprintf("", n.Namespace, n.Name) +} + +func (*BuildConfigNode) Kind() int { + return BuildConfigGraphKind +} + +type DeploymentConfigNode struct { + Node + *deploy.DeploymentConfig +} + +func (n DeploymentConfigNode) Object() interface{} { + return n.DeploymentConfig +} + +func (n DeploymentConfigNode) String() string { + return fmt.Sprintf("", n.Namespace, n.Name) +} + +func (*DeploymentConfigNode) Kind() int { + return DeploymentConfigGraphKind +} + +type ImageStreamTagNode struct { + Node + *image.ImageStream + Tag string +} + +func (n ImageStreamTagNode) ImageSpec() string { + return image.DockerImageReference{Namespace: n.Namespace, Name: n.Name, Tag: n.Tag}.String() +} + +func (n ImageStreamTagNode) ImageTag() string { + return n.Tag +} + +func (n ImageStreamTagNode) Object() interface{} { + return n.ImageStream +} + +func (n ImageStreamTagNode) String() string { + return fmt.Sprintf("", n.Namespace, n.Name, n.Tag) +} + +func (*ImageStreamTagNode) Kind() int { + return ImageStreamGraphKind +} + +type DockerImageRepositoryNode struct { + Node + Ref image.DockerImageReference +} + +func (n DockerImageRepositoryNode) ImageSpec() string { + return n.Ref.String() +} + +func (n DockerImageRepositoryNode) ImageTag() string { + return n.Ref.DockerClientDefaults().Tag +} + +func (n DockerImageRepositoryNode) String() string { + return fmt.Sprintf("", n.Ref.String()) +} + +func (*DockerImageRepositoryNode) Kind() int { + return DockerRepositoryGraphKind +} + +type SourceRepositoryNode struct { + Node + Source build.BuildSource +} + +func (n SourceRepositoryNode) String() string { + if n.Source.Git != nil { + return fmt.Sprintf("", n.Source.Git.URI, n.Source.Git.Ref) + } + return fmt.Sprintf("") +} + +func (SourceRepositoryNode) Kind() int { + return SourceRepositoryGraphKind +} + +// Service adds the provided service to the graph if it does not already exist. It does not +// link the service to covered nodes (that is a separate method). +func Service(g MutableUniqueGraph, svc *kapi.Service) graph.Node { + return EnsureUnique(g, + UniqueName(fmt.Sprintf("%d|%s/%s", ServiceGraphKind, svc.Namespace, svc.Name)), + func(node Node) graph.Node { + return &ServiceNode{node, svc} + }, + ) +} + +// DockerRepository adds the named Docker repository tag reference to the graph if it does +// not already exist. If the reference is invalid, the Name field of the graph will be +// used directly. +func DockerRepository(g MutableUniqueGraph, name, tag string) graph.Node { + ref, err := image.ParseDockerImageReference(name) + if err == nil { + if len(tag) != 0 { + ref.Tag = tag + } + if len(ref.Tag) == 0 { + ref.Tag = "latest" + } + if len(ref.Registry) == 0 { + ref.Registry = "index.docker.io" + } + if len(ref.Namespace) == 0 { + ref.Namespace = image.DockerDefaultNamespace + } + // TODO: canonicalize + name = ref.String() + } else { + ref = image.DockerImageReference{Name: name} + } + return EnsureUnique(g, + UniqueName(fmt.Sprintf("%d|%s", DockerRepositoryGraphKind, name)), + func(node Node) graph.Node { + return &DockerImageRepositoryNode{node, ref} + }, + ) +} + +// SourceRepository adds the specific BuildSource to the graph if it does not already exist. +func SourceRepository(g MutableUniqueGraph, source build.BuildSource) (graph.Node, bool) { + var sourceType, uri, ref string + switch { + case source.Git != nil: + sourceType, uri, ref = "git", source.Git.URI, source.Git.Ref + default: + return nil, false + } + return EnsureUnique(g, + UniqueName(fmt.Sprintf("%d|%s|%s#%s", SourceRepositoryGraphKind, sourceType, uri, ref)), + func(node Node) graph.Node { + return &SourceRepositoryNode{node, source} + }, + ), true +} + +// ImageStreamTag adds a graph node for the specific tag in an Image Repository if it +// does not already exist. +func ImageStreamTag(g MutableUniqueGraph, namespace, name, tag string) graph.Node { + if len(tag) == 0 { + tag = "latest" + } + return EnsureUnique(g, + UniqueName(fmt.Sprintf("%d|%s/%s:%s", ImageStreamGraphKind, namespace, name, tag)), + func(node Node) graph.Node { + return &ImageStreamTagNode{node, &image.ImageStream{ + ObjectMeta: kapi.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + }, tag} + }, + ) +} + +// BuildConfig adds a graph node for the specific build config if it does not exist, +// and will link the build config to other nodes for the images and source repositories +// it depends on. +func BuildConfig(g MutableUniqueGraph, config *build.BuildConfig) graph.Node { + node, found := g.FindOrCreate( + UniqueName(fmt.Sprintf("%d|%s/%s", BuildConfigGraphKind, config.Namespace, config.Name)), + func(node Node) graph.Node { + return &BuildConfigNode{node, config} + }, + ) + if found { + return node + } + + output := config.Parameters.Output + to := output.To + switch { + case to != nil && len(to.Name) > 0: + out := ImageStreamTag(g, defaultNamespace(to.Namespace, config.Namespace), to.Name, output.Tag) + g.AddEdge(node, out, BuildOutputGraphEdgeKind) + case len(output.DockerImageReference) > 0: + out := DockerRepository(g, output.DockerImageReference, output.Tag) + g.AddEdge(node, out, BuildOutputGraphEdgeKind) + } + + if in, ok := SourceRepository(g, config.Parameters.Source); ok { + g.AddEdge(in, node, BuildInputGraphEdgeKind) + } + + for _, trigger := range config.Triggers { + if trigger.ImageChange != nil { + t := trigger.ImageChange + from := t.From + if len(from.Name) == 0 { + continue + } + in := ImageStreamTag(g, defaultNamespace(from.Namespace, config.Namespace), from.Name, t.Tag) + g.AddEdge(in, node, BuildInputImageGraphEdgeKind) + } + } + return node +} + +// DeploymentConfig adds the provided deployment config to the graph if it does not exist, and +// will create edges that point to named Docker image repositories for each image used in the deployment. +func DeploymentConfig(g MutableUniqueGraph, config *deploy.DeploymentConfig) graph.Node { + node, found := g.FindOrCreate( + UniqueName(fmt.Sprintf("%d|%s/%s", DeploymentConfigGraphKind, config.Namespace, config.Name)), + func(node Node) graph.Node { + return &DeploymentConfigNode{node, config} + }, + ) + if found { + return node + } + if template := config.Template.ControllerTemplate.Template; template != nil { + EachTemplateImage( + &template.Spec, + DeploymentConfigHasTrigger(config), + func(image TemplateImage, err error) { + if err != nil { + return + } + if image.From != nil { + if len(image.From.Name) == 0 { + return + } + in := ImageStreamTag(g, image.From.Namespace, image.From.Name, image.FromTag) + g.AddEdge(in, node, TriggersDeploymentGraphEdgeKind) + return + } + + tag := image.Ref.Tag + image.Ref.Tag = "" + in := DockerRepository(g, image.Ref.String(), tag) + g.AddEdge(in, node, UsedInDeploymentGraphEdgeKind) + }) + } + return node +} + +// CoverServices ensures that a directed edge exists between all deployment configs and the +// services that expose them (via label selectors). +func CoverServices(g Graph) Graph { + nodes := g.NodeList() + for _, node := range nodes { + switch svc := node.(type) { + case *ServiceNode: + if svc.Service.Spec.Selector == nil { + continue + } + query := labels.SelectorFromSet(svc.Service.Spec.Selector) + for _, n := range nodes { + switch target := n.(type) { + case *DeploymentConfigNode: + template := target.DeploymentConfig.Template.ControllerTemplate.Template + if template == nil { + continue + } + if query.Matches(labels.Set(template.Labels)) { + g.AddEdge(target, svc, ExposedThroughServiceGraphEdgeKind) + } + } + } + } + } + return g +} + +func defaultNamespace(value, defaultValue string) string { + if len(value) == 0 { + return defaultValue + } + return value +} diff --git a/pkg/cmd/cli/cli.go b/pkg/cmd/cli/cli.go index aa8d00c42ed0..d1bb59a057a8 100644 --- a/pkg/cmd/cli/cli.go +++ b/pkg/cmd/cli/cli.go @@ -64,6 +64,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command { cmds.AddCommand(cmd.NewCmdLogin(f, in, out)) cmds.AddCommand(cmd.NewCmdProject(f, out)) cmds.AddCommand(cmd.NewCmdNewApplication(fullName, f, out)) + cmds.AddCommand(cmd.NewCmdStatus(fullName, f, out)) cmds.AddCommand(cmd.NewCmdStartBuild(fullName, f, out)) cmds.AddCommand(cmd.NewCmdCancelBuild(fullName, f, out)) cmds.AddCommand(cmd.NewCmdBuildLogs(fullName, f, out)) diff --git a/pkg/cmd/cli/cmd/status.go b/pkg/cmd/cli/cmd/status.go new file mode 100644 index 000000000000..e566c6abef6e --- /dev/null +++ b/pkg/cmd/cli/cmd/status.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + + "github.com/openshift/origin/pkg/cmd/cli/describe" + "github.com/openshift/origin/pkg/cmd/util/clientcmd" +) + +const statusLongDesc = ` +Show a high level overview of the current project. Links components by their relationships. +For more information about individual items, use the describe command (e.g. osc describe buildConfig, +osc describe deploymentConfig, osc describe service). +` + +func NewCmdStatus(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "status", + Short: "Show an overview of the current project", + Long: fmt.Sprintf(statusLongDesc, fullName), + Run: func(cmd *cobra.Command, args []string) { + err := RunStatus(f, out) + cmdutil.CheckErr(err) + }, + } + return cmd +} + +func RunStatus(f *clientcmd.Factory, out io.Writer) error { + client, kclient, err := f.Clients() + if err != nil { + return err + } + + namespace, err := f.DefaultNamespace() + if err != nil { + return err + } + + describer := &describe.ProjectStatusDescriber{kclient, client} + s, err := describer.Describe(namespace, "") + if err != nil { + return err + } + + fmt.Fprintf(out, s) + return nil +} diff --git a/pkg/cmd/cli/describe/describer.go b/pkg/cmd/cli/describe/describer.go index d6a881218704..34561b682136 100644 --- a/pkg/cmd/cli/describe/describer.go +++ b/pkg/cmd/cli/describe/describer.go @@ -2,6 +2,7 @@ package describe import ( "fmt" + "io" "reflect" "strconv" "strings" @@ -11,11 +12,14 @@ import ( kerrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" kctl "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/docker/docker/pkg/parsers" + "github.com/openshift/origin/pkg/api/graph" authorizationapi "github.com/openshift/origin/pkg/authorization/api" buildapi "github.com/openshift/origin/pkg/build/api" buildutil "github.com/openshift/origin/pkg/build/util" @@ -40,10 +44,6 @@ func DescriberFor(kind string, c *client.Client, kclient kclient.Interface, host return &IdentityDescriber{c}, true case "Image": return &ImageDescriber{c}, true - case "ImageRepository": - return &ImageRepositoryDescriber{c}, true - case "ImageRepositoryTag": - return &ImageRepositoryTagDescriber{c}, true case "ImageStream": return &ImageStreamDescriber{c}, true case "ImageStreamTag": @@ -294,26 +294,6 @@ func describeImage(image *imageapi.Image) (string, error) { }) } -// ImageRepositoryTagDescriber generates information about a ImageRepositoryTag (Image). -type ImageRepositoryTagDescriber struct { - client.Interface -} - -func (d *ImageRepositoryTagDescriber) Describe(namespace, name string) (string, error) { - c := d.ImageRepositoryTags(namespace) - repo, tag := parsers.ParseRepositoryTag(name) - if tag == "" { - // TODO use repo's preferred default, when that's coded - tag = "latest" - } - image, err := c.Get(repo, tag) - if err != nil { - return "", err - } - - return describeImage(image) -} - // ImageStreamTagDescriber generates information about a ImageStreamTag (Image). type ImageStreamTagDescriber struct { client.Interface @@ -350,26 +330,6 @@ func (d *ImageStreamImageDescriber) Describe(namespace, name string) (string, er return describeImage(image) } -// ImageRepositoryDescriber generates information about a ImageRepository -type ImageRepositoryDescriber struct { - client.Interface -} - -func (d *ImageRepositoryDescriber) Describe(namespace, name string) (string, error) { - c := d.ImageRepositories(namespace) - imageRepository, err := c.Get(name) - if err != nil { - return "", err - } - - return tabbedString(func(out *tabwriter.Writer) error { - formatMeta(out, imageRepository.ObjectMeta) - formatString(out, "Tags", formatLabels(imageRepository.Tags)) - formatString(out, "Registry", imageRepository.Status.DockerImageRepository) - return nil - }) -} - // ImageStreamDescriber generates information about a ImageStream type ImageStreamDescriber struct { client.Interface @@ -431,6 +391,198 @@ func (d *ProjectDescriber) Describe(namespace, name string) (string, error) { }) } +// ProjectStatusDescriber generates extended information about a Project +type ProjectStatusDescriber struct { + K kclient.Interface + C client.Interface +} + +func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error) { + project, err := d.C.Projects().Get(namespace) + if err != nil { + return "", err + } + + svcs, err := d.K.Services(namespace).List(labels.Everything()) + if err != nil { + return "", err + } + + bcs, err := d.C.BuildConfigs(namespace).List(labels.Everything(), fields.Everything()) + if err != nil { + return "", err + } + + dcs, err := d.C.DeploymentConfigs(namespace).List(labels.Everything(), fields.Everything()) + if err != nil { + return "", err + } + + g := graph.New() + for i := range bcs.Items { + graph.BuildConfig(g, &bcs.Items[i]) + } + for i := range dcs.Items { + graph.DeploymentConfig(g, &dcs.Items[i]) + } + for i := range svcs.Items { + graph.Service(g, &svcs.Items[i]) + } + groups := graph.ServiceAndDeploymentGroups(graph.CoverServices(g)) + + return tabbedString(func(out *tabwriter.Writer) error { + indent := " " + if len(project.DisplayName) > 0 && project.DisplayName != namespace { + fmt.Fprintf(out, "In project %s (%s)\n", project.DisplayName, namespace) + } else { + fmt.Fprintf(out, "In project %s\n", namespace) + } + + for _, group := range groups { + if len(group.Builds) != 0 { + for _, flow := range group.Builds { + if flow.Image != nil { + if flow.Build != nil { + fmt.Fprintf(out, "\n%s -> build %s\n", flow.Image.ImageSpec, flow.Build.Name) + } + } else { + fmt.Fprintf(out, "\nbuild %s\n", flow.Build.Name) + } + } + continue + } + if len(group.Services) == 0 { + for _, deploy := range group.Deployments { + fmt.Fprintln(out) + printLines(out, indent, 0, describeDeploymentInServiceGroup(deploy)...) + } + continue + } + fmt.Fprintln(out) + for _, svc := range group.Services { + printLines(out, indent, 0, describeServiceInServiceGroup(svc)...) + } + for _, deploy := range group.Deployments { + printLines(out, indent, 1, describeDeploymentInServiceGroup(deploy)...) + } + } + + if len(groups) == 0 { + fmt.Fprintln(out, "\nYou have no services, deployment configs, or build configs. 'osc new-app' can be used to create applications from scratch from existing Docker images and templates.") + } else { + fmt.Fprintln(out, "\nTo see more information about a service or deployment config, use 'osc describe service ' or 'osc describe dc '.") + fmt.Fprintln(out, "You can use 'osc get pods,svc,dc,bc,builds' to see lists of each of the types described above.") + } + + return nil + }) +} + +func printLines(out io.Writer, indent string, depth int, lines ...string) { + for i, s := range lines { + fmt.Fprintf(out, strings.Repeat(indent, depth)) + if i != 0 { + fmt.Fprint(out, indent) + } + fmt.Fprintln(out, s) + } +} + +func describeDeploymentInServiceGroup(deploy graph.DeploymentFlow) []string { + if len(deploy.Images) == 1 { + return []string{fmt.Sprintf("%s deploys %s", deploy.Deployment.Name, describeImageInPipeline(deploy.Images[0], deploy.Deployment.Namespace))} + } + lines := []string{fmt.Sprintf("%s deploys:", deploy.Deployment.Name)} + for _, image := range deploy.Images { + lines = append(lines, fmt.Sprintf("%s", describeImageInPipeline(image, deploy.Deployment.Namespace))) + } + return lines +} + +func describeImageInPipeline(pipeline graph.ImagePipeline, namespace string) string { + switch { + case pipeline.Image != nil && pipeline.Build != nil: + return fmt.Sprintf("%s <- %s", describeImageTagInPipeline(pipeline.Image, namespace), describeBuildInPipeline(pipeline.Build.BuildConfig, pipeline.BaseImage)) + case pipeline.Image != nil: + return describeImageTagInPipeline(pipeline.Image, namespace) + case pipeline.Build != nil: + return describeBuildInPipeline(pipeline.Build.BuildConfig, pipeline.BaseImage) + default: + return "" + } +} + +func describeImageTagInPipeline(image graph.ImageTagLocation, namespace string) string { + switch t := image.(type) { + case *graph.ImageStreamTagNode: + if t.ImageStream.Namespace != namespace { + return image.ImageSpec() + } + return fmt.Sprintf("%s:%s", t.ImageStream.Name, image.ImageTag()) + default: + return image.ImageSpec() + } +} + +func describeBuildInPipeline(build *buildapi.BuildConfig, baseImage graph.ImageTagLocation) string { + switch build.Parameters.Strategy.Type { + case buildapi.DockerBuildStrategyType: + // TODO: handle case where no source repo + source, ok := describeSourceInPipeline(&build.Parameters.Source) + if !ok { + return "docker build; no source set" + } + return fmt.Sprintf("docker build of %s", source) + case buildapi.STIBuildStrategyType: + source, ok := describeSourceInPipeline(&build.Parameters.Source) + if !ok { + return fmt.Sprintf("unconfigured source build %s", build.Name) + } + if baseImage == nil { + return fmt.Sprintf("%s; no image set", source) + } + return fmt.Sprintf("building %s on %s", source, baseImage.ImageSpec()) + case buildapi.CustomBuildStrategyType: + source, ok := describeSourceInPipeline(&build.Parameters.Source) + if !ok { + return fmt.Sprintf("custom build %s", build.Name) + } + return fmt.Sprintf("custom build of %s", source) + default: + return fmt.Sprintf("unrecognized build %s", build.Name) + } +} + +func describeSourceInPipeline(source *buildapi.BuildSource) (string, bool) { + switch source.Type { + case buildapi.BuildSourceGit: + if len(source.Git.Ref) == 0 { + return source.Git.URI, true + } + return fmt.Sprintf("%s#%s", source.Git.URI, source.Git.Ref), true + } + return "", false +} + +func describeServiceInServiceGroup(svc graph.ServiceReference) []string { + spec := svc.Service.Spec + ip := spec.PortalIP + var port string + if spec.TargetPort.String() == "0" || ip == "None" { + port = fmt.Sprintf(":%d", spec.Port) + } else { + port = fmt.Sprintf(":%d -> %s", spec.Port, spec.TargetPort.String()) + } + switch { + case ip == "None": + return []string{fmt.Sprintf("service %s (headless%s)", svc.Service.Name, port)} + case len(ip) == 0: + return []string{fmt.Sprintf("service %s (initializing%s)", svc.Service.Name, port)} + default: + return []string{fmt.Sprintf("service %s (%s%s)", svc.Service.Name, ip, port)} + } +} + // PolicyDescriber generates information about a Project type PolicyDescriber struct { client.Interface diff --git a/pkg/cmd/cli/describe/describer_test.go b/pkg/cmd/cli/describe/describer_test.go index a28b8c5b09d9..a672a288a51b 100644 --- a/pkg/cmd/cli/describe/describer_test.go +++ b/pkg/cmd/cli/describe/describer_test.go @@ -26,7 +26,7 @@ func TestDescribeFor(t *testing.T) { c := &client.Client{} testTypesList := []string{ "Build", "BuildConfig", "BuildLog", "Deployment", "DeploymentConfig", - "Image", "ImageRepository", "ImageRepositoryTag", "ImageStreamImage", + "Image", "ImageStream", "ImageStreamTag", "ImageStreamImage", "Route", "Project", } for _, o := range testTypesList { @@ -47,8 +47,8 @@ func TestDescribers(t *testing.T) { &BuildLogDescriber{c}, &DeploymentDescriber{c}, &ImageDescriber{c}, - &ImageRepositoryDescriber{c}, - &ImageRepositoryTagDescriber{c}, + &ImageStreamDescriber{c}, + &ImageStreamTagDescriber{c}, &ImageStreamImageDescriber{c}, &RouteDescriber{c}, &ProjectDescriber{c}, diff --git a/pkg/generate/app/pipeline.go b/pkg/generate/app/pipeline.go index d158c85fe69a..55bc9d428204 100644 --- a/pkg/generate/app/pipeline.go +++ b/pkg/generate/app/pipeline.go @@ -13,7 +13,7 @@ import ( kutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util" deploy "github.com/openshift/origin/pkg/deploy/api" - imageapi "github.com/openshift/origin/pkg/image/api" + image "github.com/openshift/origin/pkg/image/api" ) type Pipeline struct { @@ -41,7 +41,7 @@ func NewBuildPipeline(from string, input *ImageRef, strategy *BuildStrategyRef, } output := &ImageRef{ - DockerImageReference: imageapi.DockerImageReference{ + DockerImageReference: image.DockerImageReference{ Name: name, Tag: "latest", }, diff --git a/pkg/image/api/helper.go b/pkg/image/api/helper.go index d756250cabba..689ab70483df 100644 --- a/pkg/image/api/helper.go +++ b/pkg/image/api/helper.go @@ -83,6 +83,14 @@ func (r DockerImageReference) DockerClientDefaults() DockerImageReference { return r } +// Minimal reduces a DockerImageReference to its minimalist form. +func (r DockerImageReference) Minimal() DockerImageReference { + if r.Tag == "latest" { + r.Tag = "" + } + return r +} + var dockerPullSpecGenerator pullSpecGenerator // String converts a DockerImageReference to a Docker pull spec. diff --git a/test/fixtures/app-scenarios/k8s-lonely-pod.json b/test/fixtures/app-scenarios/k8s-lonely-pod.json index 09ddd6f73144..8c776f73be4d 100644 --- a/test/fixtures/app-scenarios/k8s-lonely-pod.json +++ b/test/fixtures/app-scenarios/k8s-lonely-pod.json @@ -24,8 +24,7 @@ "kind": "Pod", "labels": { "name": "lonely-pod" - }, - "namespace": "example" + } } ], "kind": "List" diff --git a/test/fixtures/app-scenarios/k8s-sample-app.json b/test/fixtures/app-scenarios/k8s-sample-app.json index d2d5646004fe..93aee472b03a 100644 --- a/test/fixtures/app-scenarios/k8s-sample-app.json +++ b/test/fixtures/app-scenarios/k8s-sample-app.json @@ -57,8 +57,7 @@ "kind": "ReplicationController", "labels": { "template": "ruby-helloworld-sample" - }, - "namespace": "example" + } }, { "annotations": {}, @@ -126,8 +125,7 @@ "kind": "ReplicationController", "labels": { "template": "ruby-helloworld-sample" - }, - "namespace": "example" + } }, { "apiVersion": "v1beta1", @@ -137,7 +135,6 @@ "labels": { "template": "ruby-helloworld-sample" }, - "namespace": "example", "port": 5434, "protocol": "TCP", "selector": { @@ -153,7 +150,6 @@ "labels": { "template": "ruby-helloworld-sample" }, - "namespace": "example", "port": 5432, "protocol": "TCP", "selector": { diff --git a/test/fixtures/app-scenarios/k8s-service-pod-no-rc.json b/test/fixtures/app-scenarios/k8s-service-pod-no-rc.json index 9693a9f747cf..bf72c004ce8b 100644 --- a/test/fixtures/app-scenarios/k8s-service-pod-no-rc.json +++ b/test/fixtures/app-scenarios/k8s-service-pod-no-rc.json @@ -9,7 +9,6 @@ "labels": { "template": "hello-openshift" }, - "namespace": "example", "port": 5432, "protocol": "TCP", "selector": { @@ -40,8 +39,7 @@ "kind": "Pod", "labels": { "name": "hello-openshift" - }, - "namespace": "example" + } } ], "kind": "List" diff --git a/test/fixtures/app-scenarios/k8s-service-with-nothing.json b/test/fixtures/app-scenarios/k8s-service-with-nothing.json index c50a30960a75..8840803a9532 100644 --- a/test/fixtures/app-scenarios/k8s-service-with-nothing.json +++ b/test/fixtures/app-scenarios/k8s-service-with-nothing.json @@ -9,7 +9,6 @@ "labels": { "template": "empty-service" }, - "namespace": "example", "port": 5432, "protocol": "TCP", "selector": { diff --git a/test/fixtures/app-scenarios/k8s-unserviced-rc.json b/test/fixtures/app-scenarios/k8s-unserviced-rc.json index c2d188db13a3..f81a089b3286 100644 --- a/test/fixtures/app-scenarios/k8s-unserviced-rc.json +++ b/test/fixtures/app-scenarios/k8s-unserviced-rc.json @@ -53,12 +53,11 @@ }, "replicas": 1 }, - "id": "database-1", + "id": "database-rc-1", "kind": "ReplicationController", "labels": { "template": "ruby-helloworld-sample" - }, - "namespace": "example" + } }, { "annotations": {}, @@ -122,22 +121,20 @@ }, "replicas": 3 }, - "id": "frontend-1", + "id": "frontend-rc-1", "kind": "ReplicationController", "labels": { "template": "ruby-helloworld-sample" - }, - "namespace": "example" + } }, { "apiVersion": "v1beta1", "containerPort": 3306, - "id": "database", + "id": "database-rc", "kind": "Service", "labels": { "template": "ruby-helloworld-sample" }, - "namespace": "example", "port": 5434, "protocol": "TCP", "selector": {