Merge pull request #8 from gonum/edge

All goals met, maybe reconsider MutableGraph interface further in the future into Directed/Undirected forms.
This commit is contained in:
Jeff Juozapaitis
2014-04-06 22:51:08 -07:00
12 changed files with 584 additions and 746 deletions

View File

@@ -54,7 +54,7 @@ func (dg *DenseGraph) Degree(node graph.Node) int {
func (dg *DenseGraph) NodeList() []graph.Node {
nodes := make([]graph.Node, dg.numNodes)
for i := 0; i < dg.numNodes; i++ {
nodes[i] = GonumNode(i)
nodes[i] = Node(i)
}
return nodes
@@ -65,7 +65,7 @@ func (dg *DenseGraph) DirectedEdgeList() []graph.Edge {
for i := 0; i < dg.numNodes; i++ {
for j := 0; j < dg.numNodes; j++ {
if dg.adjacencyMatrix[i*dg.numNodes+j] != math.Inf(1) {
edges = append(edges, GonumEdge{GonumNode(i), GonumNode(j)})
edges = append(edges, Edge{Node(i), Node(j)})
}
}
}
@@ -78,70 +78,71 @@ func (dg *DenseGraph) Neighbors(node graph.Node) []graph.Node {
for i := 0; i < dg.numNodes; i++ {
if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) ||
dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
neighbors = append(neighbors, GonumNode(i))
neighbors = append(neighbors, Node(i))
}
}
return neighbors
}
func (dg *DenseGraph) IsNeighbor(node, neighbor graph.Node) bool {
return dg.adjacencyMatrix[neighbor.ID()*dg.numNodes+node.ID()] != math.Inf(1) ||
dg.adjacencyMatrix[node.ID()*dg.numNodes+neighbor.ID()] != math.Inf(1)
func (dg *DenseGraph) EdgeBetween(node, neighbor graph.Node) graph.Edge {
if dg.adjacencyMatrix[neighbor.ID()*dg.numNodes+node.ID()] != math.Inf(1) ||
dg.adjacencyMatrix[node.ID()*dg.numNodes+neighbor.ID()] != math.Inf(1) {
return Edge{node, neighbor}
}
return nil
}
func (dg *DenseGraph) Successors(node graph.Node) []graph.Node {
neighbors := make([]graph.Node, 0)
for i := 0; i < dg.numNodes; i++ {
if dg.adjacencyMatrix[node.ID()*dg.numNodes+i] != math.Inf(1) {
neighbors = append(neighbors, GonumNode(i))
neighbors = append(neighbors, Node(i))
}
}
return neighbors
}
func (dg *DenseGraph) IsSuccessor(node, succ graph.Node) bool {
return dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] != math.Inf(1)
func (dg *DenseGraph) EdgeTo(node, succ graph.Node) graph.Edge {
if dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] != math.Inf(1) {
return Edge{node, succ}
}
return nil
}
func (dg *DenseGraph) Predecessors(node graph.Node) []graph.Node {
neighbors := make([]graph.Node, 0)
for i := 0; i < dg.numNodes; i++ {
if dg.adjacencyMatrix[i*dg.numNodes+node.ID()] != math.Inf(1) {
neighbors = append(neighbors, GonumNode(i))
neighbors = append(neighbors, Node(i))
}
}
return neighbors
}
func (dg *DenseGraph) IsPredecessor(node, pred graph.Node) bool {
return dg.adjacencyMatrix[pred.ID()*dg.numNodes+node.ID()] != math.Inf(1)
}
// DenseGraph is naturally dense, we don't need to do anything
func (dg *DenseGraph) Crunch() {
}
func (dg *DenseGraph) Cost(node, succ graph.Node) float64 {
return dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()]
func (dg *DenseGraph) Cost(e graph.Edge) float64 {
return dg.adjacencyMatrix[e.Head().ID()*dg.numNodes+e.Tail().ID()]
}
// Sets the cost of the edge between node and succ. If the cost is +Inf, it will remove the edge,
// 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 (dg *DenseGraph) SetEdgeCost(node, succ graph.Node, cost float64, directed bool) {
dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] = cost
func (dg *DenseGraph) SetEdgeCost(e graph.Edge, cost float64, directed bool) {
dg.adjacencyMatrix[e.Head().ID()*dg.numNodes+e.Tail().ID()] = cost
if !directed {
dg.adjacencyMatrix[succ.ID()*dg.numNodes+node.ID()] = cost
dg.adjacencyMatrix[e.Tail().ID()*dg.numNodes+e.Head().ID()] = cost
}
}
// More or less equivalent to SetEdgeCost(node, succ, math.Inf(1), directed)
func (dg *DenseGraph) RemoveEdge(node, succ graph.Node, directed bool) {
dg.adjacencyMatrix[node.ID()*dg.numNodes+succ.ID()] = math.Inf(1)
if !directed {
dg.adjacencyMatrix[succ.ID()*dg.numNodes+node.ID()] = math.Inf(1)
}
// Equivalent to SetEdgeCost(edge, math.Inf(1), directed)
func (dg *DenseGraph) RemoveEdge(e graph.Edge, directed bool) {
dg.SetEdgeCost(e, math.Inf(1), directed)
}

View File

@@ -18,17 +18,17 @@ func TestBasicDenseImpassable(t *testing.T) {
}
for i := 0; i < 5; i++ {
if !dg.NodeExists(concrete.GonumNode(i)) {
if !dg.NodeExists(concrete.Node(i)) {
t.Errorf("Node that should exist doesn't: %d", i)
}
if degree := dg.Degree(concrete.GonumNode(i)); degree != 0 {
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.GonumNode(i)) {
if dg.NodeExists(concrete.Node(i)) {
t.Errorf("Node exists that shouldn't: %d")
}
}
@@ -41,17 +41,17 @@ func TestBasicDensePassable(t *testing.T) {
}
for i := 0; i < 5; i++ {
if !dg.NodeExists(concrete.GonumNode(i)) {
if !dg.NodeExists(concrete.Node(i)) {
t.Errorf("Node that should exist doesn't: %d", i)
}
if degree := dg.Degree(concrete.GonumNode(i)); degree != 10 {
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.GonumNode(i)) {
if dg.NodeExists(concrete.Node(i)) {
t.Errorf("Node exists that shouldn't: %d")
}
}
@@ -59,81 +59,81 @@ func TestBasicDensePassable(t *testing.T) {
func TestDenseAddRemove(t *testing.T) {
dg := concrete.NewDenseGraph(10, false)
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(2), 1.0, false)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 1.0, false)
if neighbors := dg.Neighbors(concrete.GonumNode(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 ||
!dg.IsNeighbor(concrete.GonumNode(0), concrete.GonumNode(2)) {
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.GonumNode(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 ||
!dg.IsSuccessor(concrete.GonumNode(0), concrete.GonumNode(2)) {
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.GonumNode(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 ||
!dg.IsPredecessor(concrete.GonumNode(0), concrete.GonumNode(2)) {
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.GonumNode(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 ||
!dg.IsNeighbor(concrete.GonumNode(2), concrete.GonumNode(0)) {
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.GonumNode(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 ||
!dg.IsSuccessor(concrete.GonumNode(2), concrete.GonumNode(0)) {
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.GonumNode(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 ||
!dg.IsPredecessor(concrete.GonumNode(2), concrete.GonumNode(0)) {
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.GonumNode(0), concrete.GonumNode(2), true)
dg.RemoveEdge(concrete.Edge{concrete.Node(0), concrete.Node(2)}, true)
if neighbors := dg.Neighbors(concrete.GonumNode(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 ||
!dg.IsNeighbor(concrete.GonumNode(0), concrete.GonumNode(2)) {
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.GonumNode(0)); len(neighbors) != 0 || dg.IsSuccessor(concrete.GonumNode(0), concrete.GonumNode(2)) {
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.GonumNode(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 ||
!dg.IsPredecessor(concrete.GonumNode(0), concrete.GonumNode(2)) {
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.GonumNode(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 ||
!dg.IsNeighbor(concrete.GonumNode(2), concrete.GonumNode(0)) {
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.GonumNode(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 ||
!dg.IsSuccessor(concrete.GonumNode(2), concrete.GonumNode(0)) {
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.GonumNode(2)); len(neighbors) != 0 || dg.IsPredecessor(concrete.GonumNode(2), concrete.GonumNode(0)) {
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.GonumNode(0), concrete.GonumNode(2), 2.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2.0, true)
// I figure we've torture tested Neighbors/Successors/Predecessors at this point
// so we'll just use the bool functions now
if !dg.IsSuccessor(concrete.GonumNode(0), concrete.GonumNode(2)) {
if dg.EdgeTo(concrete.Node(0), concrete.Node(2)) == nil {
t.Error("Adding directed edge didn't change successor back")
} else if !dg.IsSuccessor(concrete.GonumNode(2), concrete.GonumNode(0)) {
} 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.GonumNode(2), concrete.GonumNode(0)), dg.Cost(concrete.GonumNode(0), concrete.GonumNode(2)); math.Abs(c1-c2) < .000001 {
} 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.GonumNode(2), concrete.GonumNode(0), false)
if dg.IsSuccessor(concrete.GonumNode(0), concrete.GonumNode(2)) || dg.IsSuccessor(concrete.GonumNode(2), concrete.GonumNode(0)) {
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")
}
}
@@ -173,7 +173,7 @@ func TestDenseLists(t *testing.T) {
t.Errorf("Improper number of edges for passable dense graph")
}
dg.RemoveEdge(concrete.GonumNode(12), concrete.GonumNode(11), true)
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")

View File

@@ -4,29 +4,34 @@ import (
"math"
"sort"
gr "github.com/gonum/graph"
"github.com/gonum/graph"
)
// A simple int alias.
type GonumNode int
type Node int
func (node GonumNode) ID() int {
func (node Node) ID() int {
return int(node)
}
// Just a collection of two nodes
type GonumEdge struct {
H, T gr.Node
type Edge struct {
H, T graph.Node
}
func (edge GonumEdge) Head() gr.Node {
func (edge Edge) Head() graph.Node {
return edge.H
}
func (edge GonumEdge) Tail() gr.Node {
func (edge Edge) Tail() graph.Node {
return edge.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.
//
@@ -38,35 +43,32 @@ func (edge GonumEdge) Tail() gr.Node {
// 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 GonumGraph struct {
successors map[int]map[int]float64
predecessors map[int]map[int]float64
nodeMap map[int]gr.Node
directed bool
type Graph struct {
successors map[int]map[int]WeightedEdge
predecessors map[int]map[int]WeightedEdge
nodeMap map[int]graph.Node
}
func NewGonumGraph(directed bool) *GonumGraph {
return &GonumGraph{
successors: make(map[int]map[int]float64),
predecessors: make(map[int]map[int]float64),
nodeMap: make(map[int]gr.Node),
directed: directed,
func NewGraph() *Graph {
return &Graph{
successors: make(map[int]map[int]WeightedEdge),
predecessors: make(map[int]map[int]WeightedEdge),
nodeMap: make(map[int]graph.Node),
}
}
func NewPreAllocatedGonumGraph(directed bool, numVertices int) *GonumGraph {
return &GonumGraph{
successors: make(map[int]map[int]float64, numVertices),
predecessors: make(map[int]map[int]float64, numVertices),
nodeMap: make(map[int]gr.Node, numVertices),
directed: directed,
func NewPreAllocatedGraph(numVertices int) *Graph {
return &Graph{
successors: make(map[int]map[int]WeightedEdge, numVertices),
predecessors: make(map[int]map[int]WeightedEdge, numVertices),
nodeMap: make(map[int]graph.Node, numVertices),
}
}
/* Mutable Graph implementation */
func (graph *GonumGraph) NewNode(successors []gr.Node) (node gr.Node) {
nodeList := graph.NodeList()
func (gr *Graph) NewNode() (node graph.Node) {
nodeList := gr.NodeList()
ids := make([]int, len(nodeList))
for i, node := range nodeList {
ids[i] = node.ID()
@@ -76,278 +78,211 @@ func (graph *GonumGraph) NewNode(successors []gr.Node) (node gr.Node) {
sort.Sort(&nodes)
for i, node := range nodes {
if i != node {
graph.AddNode(GonumNode(i), successors)
return GonumNode(i)
gr.AddNode(Node(i))
return Node(i)
}
}
newID := len(nodes)
graph.AddNode(GonumNode(newID), successors)
return GonumNode(newID)
gr.AddNode(Node(newID))
return Node(newID)
}
func (graph *GonumGraph) AddNode(node gr.Node, successors []gr.Node) {
id := node.ID()
if _, ok := graph.successors[id]; ok {
func (gr *Graph) AddNode(node graph.Node) {
if _, ok := gr.nodeMap[node.ID()]; ok {
return
}
graph.nodeMap[id] = node
gr.nodeMap[node.ID()] = node
gr.successors[node.ID()] = make(map[int]WeightedEdge)
gr.predecessors[node.ID()] = make(map[int]WeightedEdge)
}
graph.successors[id] = make(map[int]float64, len(successors))
if !graph.directed {
graph.predecessors[id] = make(map[int]float64, len(successors))
} else {
graph.predecessors[id] = make(map[int]float64)
}
for _, successor := range successors {
succ := successor.ID()
graph.successors[id][succ] = 1.0
func (gr *Graph) AddEdge(e graph.Edge, cost float64, directed bool) {
head, tail := e.Head(), e.Tail()
gr.AddNode(head)
gr.AddNode(tail)
// Always add the reciprocal node to the graph
if _, ok := graph.successors[succ]; !ok {
graph.nodeMap[succ] = successor
graph.predecessors[succ] = make(map[int]float64)
graph.successors[succ] = make(map[int]float64)
}
graph.predecessors[succ][id] = 1.0
// But only add the reciprocal edge if we're undirected
if !graph.directed {
graph.successors[succ][id] = 1.0
graph.predecessors[id][succ] = 1.0
}
gr.successors[head.ID()][tail.ID()] = WeightedEdge{Edge: e, Cost: cost}
gr.predecessors[tail.ID()][head.ID()] = WeightedEdge{Edge: e, Cost: cost}
if !directed {
gr.successors[tail.ID()][head.ID()] = WeightedEdge{Edge: e, Cost: cost}
gr.predecessors[head.ID()][tail.ID()] = WeightedEdge{Edge: e, Cost: cost}
}
}
func (graph *GonumGraph) AddEdge(e gr.Edge) {
id := e.Head().ID()
successor := e.Tail().ID()
if _, ok := graph.successors[id]; !ok {
func (gr *Graph) RemoveNode(node graph.Node) {
if _, ok := gr.nodeMap[node.ID()]; !ok {
return
}
delete(gr.nodeMap, node.ID())
if _, ok := graph.successors[successor]; !ok {
graph.nodeMap[successor] = e.Tail()
graph.successors[successor] = make(map[int]float64)
graph.predecessors[successor] = make(map[int]float64)
for succ, _ := range gr.successors[node.ID()] {
delete(gr.predecessors[succ], node.ID())
}
delete(gr.successors, node.ID())
graph.successors[id][successor] = 1.0
graph.predecessors[successor][id] = 1.0
if !graph.directed {
graph.successors[successor][id] = 1.0
graph.predecessors[id][successor] = 1.0
for pred, _ := range gr.predecessors[node.ID()] {
delete(gr.successors[pred], node.ID())
}
}
func (graph *GonumGraph) SetEdgeCost(e gr.Edge, cost float64) {
id := e.Head().ID()
successor := e.Tail().ID()
// Normally I'd use graph.vertices.Contains(id) as above, but this is equivalent and a bit
// easier to read here
if _, ok := graph.successors[id]; !ok {
return
} else if _, ok := graph.successors[id][successor]; !ok {
return
}
graph.successors[id][successor] = cost
graph.predecessors[successor][id] = cost
// By the spec, only the empty graph will be toggled between directed and undirected.
// Therefore we can be sure the reciprocal edge exists
if !graph.directed {
graph.successors[successor][id] = cost
graph.predecessors[id][successor] = cost
}
}
func (graph *GonumGraph) RemoveNode(node gr.Node) {
id := node.ID()
if _, ok := graph.successors[id]; !ok {
return
}
delete(graph.nodeMap, id)
for succ, _ := range graph.successors[id] {
delete(graph.predecessors[succ], id)
}
delete(graph.successors, id)
for pred, _ := range graph.predecessors[id] {
delete(graph.successors[pred], id)
}
delete(graph.predecessors, id)
delete(gr.predecessors, node.ID())
}
func (graph *GonumGraph) RemoveEdge(e gr.Edge) {
id := e.Head().ID()
succ := e.Tail().ID()
if _, ok := graph.successors[id]; !ok {
func (gr *Graph) RemoveEdge(e graph.Edge, directed bool) {
head, tail := e.Head(), e.Tail()
if _, ok := gr.nodeMap[head.ID()]; !ok {
return
} else if _, ok := graph.successors[succ]; !ok {
} else if _, ok := gr.nodeMap[tail.ID()]; !ok {
return
}
delete(graph.successors[id], succ)
delete(graph.predecessors[succ], id)
if !graph.directed {
delete(graph.predecessors[id], succ)
delete(graph.successors[succ], id)
delete(gr.successors[head.ID()], tail.ID())
delete(gr.predecessors[tail.ID()], head.ID())
if !directed {
delete(gr.successors[tail.ID()], head.ID())
delete(gr.predecessors[head.ID()], tail.ID())
}
}
func (graph *GonumGraph) EmptyGraph() {
if len(graph.successors) == 0 {
return
}
graph.successors = make(map[int]map[int]float64)
graph.predecessors = make(map[int]map[int]float64)
graph.nodeMap = make(map[int]gr.Node)
}
func (graph *GonumGraph) SetDirected(directed bool) {
if len(graph.successors) > 0 {
return
}
graph.directed = directed
func (gr *Graph) EmptyGraph() {
gr.successors = make(map[int]map[int]WeightedEdge)
gr.predecessors = make(map[int]map[int]WeightedEdge)
gr.nodeMap = make(map[int]graph.Node)
}
/* Graph implementation */
func (graph *GonumGraph) Successors(node gr.Node) []gr.Node {
id := node.ID()
if _, ok := graph.successors[id]; !ok {
func (gr *Graph) Successors(node graph.Node) []graph.Node {
if _, ok := gr.successors[node.ID()]; !ok {
return nil
}
successors := make([]gr.Node, 0, len(graph.successors[id]))
for succ, _ := range graph.successors[id] {
successors = append(successors, graph.nodeMap[succ])
successors := make([]graph.Node, len(gr.successors[node.ID()]))
i := 0
for succ, _ := range gr.successors[node.ID()] {
successors[i] = gr.nodeMap[succ]
i++
}
return successors
}
func (graph *GonumGraph) IsSuccessor(node, successor gr.Node) bool {
succ := successor.ID()
id := node.ID()
if _, ok := graph.successors[id]; !ok {
return false
}
_, ok := graph.successors[id][succ]
return ok
}
func (graph *GonumGraph) Predecessors(node gr.Node) []gr.Node {
id := node.ID()
if _, ok := graph.successors[id]; !ok {
func (gr *Graph) EdgeTo(node, succ graph.Node) graph.Edge {
if _, ok := gr.nodeMap[node.ID()]; !ok {
return nil
} else if _, ok := gr.nodeMap[succ.ID()]; !ok {
return nil
}
predecessors := make([]gr.Node, 0, len(graph.predecessors[id]))
for pred, _ := range graph.predecessors[id] {
predecessors = append(predecessors, graph.nodeMap[pred])
edge, ok := gr.successors[node.ID()][succ.ID()]
if !ok {
return nil
}
return edge
}
func (gr *Graph) Predecessors(node graph.Node) []graph.Node {
if _, ok := gr.successors[node.ID()]; !ok {
return nil
}
predecessors := make([]graph.Node, len(gr.predecessors[node.ID()]))
i := 0
for succ, _ := range gr.predecessors[node.ID()] {
predecessors[i] = gr.nodeMap[succ]
i++
}
return predecessors
}
func (graph *GonumGraph) IsPredecessor(node, predecessor gr.Node) bool {
id := node.ID()
pred := predecessor.ID()
if _, ok := graph.successors[id]; !ok {
return false
}
_, ok := graph.predecessors[id][pred]
return ok
}
func (graph *GonumGraph) Neighbors(node gr.Node) []gr.Node {
id := node.ID()
if _, ok := graph.successors[id]; !ok {
func (gr *Graph) Neighbors(node graph.Node) []graph.Node {
if _, ok := gr.successors[node.ID()]; !ok {
return nil
}
neighbors := make([]gr.Node, 0, len(graph.predecessors[id])+len(graph.successors[id]))
for succ, _ := range graph.successors[id] {
neighbors = append(neighbors, graph.nodeMap[succ])
neighbors := make([]graph.Node, len(gr.predecessors[node.ID()])+len(gr.successors[node.ID()]))
i := 0
for succ, _ := range gr.successors[node.ID()] {
neighbors[i] = gr.nodeMap[succ]
i++
}
for pred, _ := range graph.predecessors[id] {
for pred, _ := range gr.predecessors[node.ID()] {
// We should only add the predecessor if it wasn't already added from successors
if _, ok := graph.successors[id][pred]; !ok {
neighbors = append(neighbors, graph.nodeMap[pred])
if _, ok := gr.successors[node.ID()][pred]; !ok {
neighbors[i] = gr.nodeMap[pred]
i++
}
}
return neighbors
}
func (graph *GonumGraph) IsNeighbor(node, neigh gr.Node) bool {
id := node.ID()
neighbor := neigh.ID()
if _, ok := graph.successors[id]; !ok {
return false
func (gr *Graph) EdgeBetween(node, neigh graph.Node) graph.Edge {
e := gr.EdgeTo(node, neigh)
if e != nil {
return e
}
_, succ := graph.predecessors[id][neighbor]
_, pred := graph.predecessors[id][neighbor]
e = gr.EdgeTo(neigh, node)
if e != nil {
return e
}
return succ || pred
return nil
}
func (graph *GonumGraph) NodeExists(node gr.Node) bool {
_, ok := graph.successors[node.ID()]
func (gr *Graph) NodeExists(node graph.Node) bool {
_, ok := gr.nodeMap[node.ID()]
return ok
}
func (graph *GonumGraph) Degree(node gr.Node) int {
id := node.ID()
if _, ok := graph.successors[id]; !ok {
func (gr *Graph) Degree(node graph.Node) int {
if _, ok := gr.nodeMap[node.ID()]; !ok {
return 0
}
return len(graph.successors[id]) + len(graph.predecessors[id])
return len(gr.successors[node.ID()]) + len(gr.predecessors[node.ID()])
}
func (graph *GonumGraph) EdgeList() []gr.Edge {
eList := make([]gr.Edge, 0, len(graph.successors))
for id, succMap := range graph.successors {
for succ, _ := range succMap {
eList = append(eList, GonumEdge{graph.nodeMap[id], graph.nodeMap[succ]})
}
}
return eList
}
func (graph *GonumGraph) NodeList() []gr.Node {
nodes := make([]gr.Node, 0, len(graph.successors))
for _, node := range graph.nodeMap {
nodes = append(nodes, node)
func (gr *Graph) NodeList() []graph.Node {
nodes := make([]graph.Node, len(gr.successors))
i := 0
for _, node := range gr.nodeMap {
nodes[i] = node
i++
}
return nodes
}
func (graph *GonumGraph) IsDirected() bool {
return graph.directed
}
func (graph *GonumGraph) Cost(node, succ gr.Node) float64 {
if s, ok := graph.successors[node.ID()]; ok {
if c, ok := s[succ.ID()]; ok {
return c
func (gr *Graph) Cost(e graph.Edge) float64 {
if s, ok := gr.successors[e.Head().ID()]; ok {
if we, ok := s[e.Tail().ID()]; ok {
return we.Cost
}
}
return math.Inf(1)
}
func (gr *Graph) EdgeList() []graph.Edge {
edgeList := make([]graph.Edge, 0, len(gr.successors))
edgeMap := make(map[int]map[int]struct{}, len(gr.successors))
for node, succMap := range gr.successors {
edgeMap[node] = make(map[int]struct{}, len(succMap))
for succ, edge := range succMap {
if doneMap, ok := edgeMap[succ]; ok {
if _, ok := doneMap[node]; ok {
continue
}
}
edgeList = append(edgeList, edge)
edgeMap[node][succ] = struct{}{}
}
}
return edgeList
}

View File

@@ -1,80 +1,14 @@
package concrete_test
import (
"testing"
_ "testing"
gr "github.com/gonum/graph"
"github.com/gonum/graph"
"github.com/gonum/graph/concrete"
)
var _ gr.Graph = &concrete.GonumGraph{}
var _ gr.DirectedGraph = &concrete.GonumGraph{}
var _ gr.MutableGraph = &concrete.GonumGraph{}
var _ gr.EdgeListGraph = &concrete.GonumGraph{}
var _ graph.Graph = &concrete.Graph{}
var _ graph.DirectedGraph = &concrete.Graph{}
var _ graph.MutableGraph = &concrete.Graph{}
// exercise methods for 100% coverage.
func TestGonumGraphCoverage(t *testing.T) {
var n1 gr.Node = concrete.GonumNode(1)
var e gr.Edge = concrete.GonumEdge{n1, n1}
e.Head()
e.Tail()
concrete.NewPreAllocatedGonumGraph(true, 1)
dg := concrete.NewGonumGraph(true) // directed
var dm gr.MutableGraph = dg // directed mutable
var um gr.MutableGraph = concrete.NewGonumGraph(false) // undirected
dm.AddNode(n1, nil)
n0 := dm.NewNode(nil) // fill in hole, node 0
dm.NewNode(nil) // node 2
dm.AddNode(n1, nil) // no op
um.AddNode(n1, nil) // undirected
n3 := concrete.GonumNode(3)
n4 := concrete.GonumNode(4)
dm.AddNode(n3, []gr.Node{n4}) // both node and successor are new
um.AddNode(n0, []gr.Node{n1}) // new undirected edge
dm.AddEdge(e)
n5 := concrete.GonumNode(5)
dm.AddEdge(concrete.GonumEdge{n5, n1}) // head not in graph
dm.AddEdge(concrete.GonumEdge{n1, n5}) // tail not in graph
um.AddEdge(concrete.GonumEdge{n1, n0}) // undirected
n6 := concrete.GonumNode(6)
dm.SetEdgeCost(concrete.GonumEdge{n6, n1}, 0) // n6 not in graph
dm.SetEdgeCost(concrete.GonumEdge{n1, n6}, 0)
um.SetEdgeCost(concrete.GonumEdge{n0, n1}, 0) // undirected
dm.AddEdge(concrete.GonumEdge{n5, n0})
dm.RemoveNode(n5)
dm.RemoveNode(n5)
dm.RemoveEdge(concrete.GonumEdge{n6, n0})
dm.RemoveEdge(concrete.GonumEdge{n0, n6})
um.RemoveEdge(concrete.GonumEdge{n0, n1})
dm.SetDirected(false)
dm.EmptyGraph()
dm.EmptyGraph()
dm.SetDirected(true)
d := dm.(gr.DirectedGraph)
d.Successors(n0)
dm.AddNode(n0, []gr.Node{n1})
d.Successors(n0)
d.IsSuccessor(n3, n1)
d.IsSuccessor(n1, n3)
d.Predecessors(n3)
d.Predecessors(n1)
d.IsPredecessor(n3, n1)
d.IsPredecessor(n1, n3)
u := um.(gr.Graph)
u.Neighbors(n3)
um.AddEdge(concrete.GonumEdge{n0, n1})
u.Neighbors(n1)
dg.Neighbors(n1) // directed graph neighbors
d.IsNeighbor(n3, n1)
d.IsNeighbor(n1, n3)
d.NodeExists(n1)
d.Degree(n3)
um.AddEdge(concrete.GonumEdge{n0, n0})
d.Degree(n0)
u.Degree(n0)
u.NodeList()
u.(gr.EdgeLister).EdgeList()
dg.IsDirected()
dm.Cost(n0, n1)
dm.Cost(n6, n1)
}
// var _ gr.EdgeListGraph = &concrete.Graph{}

View File

@@ -5,7 +5,7 @@ import (
"math"
"strings"
gr "github.com/gonum/graph"
"github.com/gonum/graph"
)
type TileGraph struct {
@@ -68,20 +68,20 @@ func GenerateTileGraph(template string) (*TileGraph, error) {
}, nil
}
func (graph *TileGraph) SetPassability(row, col int, passability bool) {
loc := row*graph.numCols + col
if loc >= len(graph.tiles) || row < 0 || col < 0 {
func (gr *TileGraph) SetPassability(row, col int, passability bool) {
loc := row*gr.numCols + col
if loc >= len(gr.tiles) || row < 0 || col < 0 {
return
}
graph.tiles[loc] = passability
gr.tiles[loc] = passability
}
func (graph *TileGraph) String() string {
func (gr *TileGraph) String() string {
var outString string
for r := 0; r < graph.numRows; r++ {
for c := 0; c < graph.numCols; c++ {
if graph.tiles[r*graph.numCols+c] == false {
for r := 0; r < gr.numRows; r++ {
for c := 0; c < gr.numCols; c++ {
if gr.tiles[r*gr.numCols+c] == false {
outString += "\u2580" // Black square
} else {
outString += " " // Space
@@ -94,15 +94,15 @@ func (graph *TileGraph) String() string {
return outString[:len(outString)-1] // Kill final newline
}
func (graph *TileGraph) PathString(path []gr.Node) string {
func (gr *TileGraph) PathString(path []graph.Node) string {
if path == nil || len(path) == 0 {
return graph.String()
return gr.String()
}
var outString string
for r := 0; r < graph.numRows; r++ {
for c := 0; c < graph.numCols; c++ {
if id := r*graph.numCols + c; graph.tiles[id] == false {
for r := 0; r < gr.numRows; r++ {
for c := 0; c < gr.numCols; c++ {
if id := r*gr.numCols + c; gr.tiles[id] == false {
outString += "\u2580" // Black square
} else if id == path[0].ID() {
outString += "s"
@@ -125,47 +125,47 @@ func (graph *TileGraph) PathString(path []gr.Node) string {
return outString[:len(outString)-1]
}
func (graph *TileGraph) Dimensions() (rows, cols int) {
return graph.numRows, graph.numCols
func (gr *TileGraph) Dimensions() (rows, cols int) {
return gr.numRows, gr.numCols
}
func (graph *TileGraph) IDToCoords(id int) (row, col int) {
col = (id % graph.numCols)
row = (id - col) / graph.numCols
func (gr *TileGraph) IDToCoords(id int) (row, col int) {
col = (id % gr.numCols)
row = (id - col) / gr.numCols
return row, col
}
func (graph *TileGraph) CoordsToID(row, col int) (id int) {
if row < 0 || row >= graph.numRows || col < 0 || col >= graph.numCols {
func (gr *TileGraph) CoordsToID(row, col int) (id int) {
if row < 0 || row >= gr.numRows || col < 0 || col >= gr.numCols {
return -1
}
id = row*graph.numCols + col
id = row*gr.numCols + col
return id
}
func (graph *TileGraph) CoordsToNode(row, col int) (node gr.Node) {
id := graph.CoordsToID(row, col)
func (gr *TileGraph) CoordsToNode(row, col int) (node graph.Node) {
id := gr.CoordsToID(row, col)
if id == -1 {
return nil
} else {
return GonumNode(id)
return Node(id)
}
}
func (graph *TileGraph) successors(node gr.Node) []gr.Node {
func (gr *TileGraph) Neighbors(node graph.Node) []graph.Node {
id := node.ID()
if id < 0 || id >= len(graph.tiles) || graph.tiles[id] == false {
if !gr.NodeExists(node) {
return nil
}
row, col := graph.IDToCoords(id)
row, col := gr.IDToCoords(id)
neighbors := []gr.Node{graph.CoordsToNode(row-1, col), graph.CoordsToNode(row+1, col), graph.CoordsToNode(row, col-1), graph.CoordsToNode(row, col+1)}
realNeighbors := make([]gr.Node, 0, 4) // Will overallocate sometimes, but not by much. Not a big deal
neighbors := []graph.Node{gr.CoordsToNode(row-1, col), gr.CoordsToNode(row+1, col), gr.CoordsToNode(row, col-1), gr.CoordsToNode(row, col+1)}
realNeighbors := make([]graph.Node, 0, 4) // Will overallocate sometimes, but not by much. Not a big deal
for _, neighbor := range neighbors {
if neighbor != nil && graph.tiles[neighbor.ID()] == true {
if neighbor != nil && gr.tiles[neighbor.ID()] == true {
realNeighbors = append(realNeighbors, neighbor)
}
}
@@ -173,72 +173,61 @@ func (graph *TileGraph) successors(node gr.Node) []gr.Node {
return realNeighbors
}
func (graph *TileGraph) isSuccessor(node, successor gr.Node) bool {
id, succ := node.ID(), successor.ID()
return (id >= 0 && id < len(graph.tiles) && graph.tiles[id] == true) && (succ >= 0 && succ < len(graph.tiles) && graph.tiles[succ] == true)
func (gr *TileGraph) EdgeBetween(node, neighbor graph.Node) graph.Edge {
if !gr.NodeExists(node) || !gr.NodeExists(neighbor) {
return nil
}
r1, c1 := gr.IDToCoords(node.ID())
r2, c2 := gr.IDToCoords(neighbor.ID())
if (c1 == c2 && (r2 == r1+1 || r2 == r1-1)) || (r1 == r2 && (c2 == c1+1 || c2 == c1-1)) {
return Edge{node, neighbor}
}
return nil
}
func (graph *TileGraph) predecessors(node gr.Node) []gr.Node {
return graph.successors(node)
}
func (graph *TileGraph) isPredecessor(node, pred gr.Node) bool {
return graph.isSuccessor(node, pred)
}
func (graph *TileGraph) Neighbors(node gr.Node) []gr.Node {
return graph.successors(node)
}
func (graph *TileGraph) IsNeighbor(id, neighbor gr.Node) bool {
return graph.isSuccessor(id, neighbor)
}
func (graph *TileGraph) NodeExists(node gr.Node) bool {
func (gr *TileGraph) NodeExists(node graph.Node) bool {
id := node.ID()
return id >= 0 && id < len(graph.tiles) && graph.tiles[id] == true
return id >= 0 && id < len(gr.tiles) && gr.tiles[id] == true
}
func (graph *TileGraph) Degree(node gr.Node) int {
return len(graph.successors(node)) * 2
func (gr *TileGraph) Degree(node graph.Node) int {
return len(gr.Neighbors(node)) * 2
}
func (graph *TileGraph) EdgeList() []gr.Edge {
edges := make([]gr.Edge, 0)
for id, passable := range graph.tiles {
func (gr *TileGraph) EdgeList() []graph.Edge {
edges := make([]graph.Edge, 0)
for id, passable := range gr.tiles {
if !passable {
continue
}
for _, succ := range graph.successors(GonumNode(id)) {
edges = append(edges, GonumEdge{GonumNode(id), succ})
for _, succ := range gr.Neighbors(Node(id)) {
edges = append(edges, Edge{Node(id), succ})
}
}
return edges
}
func (graph *TileGraph) NodeList() []gr.Node {
nodes := make([]gr.Node, 0)
for id, passable := range graph.tiles {
func (gr *TileGraph) NodeList() []graph.Node {
nodes := make([]graph.Node, 0)
for id, passable := range gr.tiles {
if !passable {
continue
}
nodes = append(nodes, GonumNode(id))
nodes = append(nodes, Node(id))
}
return nodes
}
func (graph *TileGraph) IsDirected() bool {
return false
}
func (graph *TileGraph) Cost(node1, node2 gr.Node) float64 {
if graph.IsNeighbor(node1, node2) {
func (gr *TileGraph) Cost(e graph.Edge) float64 {
if edge := gr.EdgeBetween(e.Head(), e.Tail()); edge != nil {
return 1.0
} else {
return math.Inf(1)
}
return math.Inf(1)
}

View File

@@ -3,11 +3,11 @@ package concrete_test
import (
"testing"
gr "github.com/gonum/graph"
"github.com/gonum/graph"
"github.com/gonum/graph/concrete"
)
var _ gr.Graph = (*concrete.TileGraph)(nil)
var _ graph.Graph = (*concrete.TileGraph)(nil)
func TestTileGraph(t *testing.T) {
tg := concrete.NewTileGraph(4, 4, false)
@@ -77,11 +77,11 @@ func TestTileGraph(t *testing.T) {
t.Error("ID to Coords fails on 3,0")
}
if succ := tg.Neighbors(concrete.GonumNode(0)); succ != nil || len(succ) != 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.GonumNode(2)); succ == nil || len(succ) != 2 {
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 {
@@ -91,45 +91,14 @@ func TestTileGraph(t *testing.T) {
}
}
if tg.Degree(concrete.GonumNode(2)) != 4 {
if tg.Degree(concrete.Node(2)) != 4 {
t.Error("Degree returns incorrect number for (0,2)")
}
if tg.Degree(concrete.GonumNode(1)) != 2 {
if tg.Degree(concrete.Node(1)) != 2 {
t.Error("Degree returns incorrect number for (0,2)")
}
if tg.Degree(concrete.GonumNode(0)) != 0 {
if tg.Degree(concrete.Node(0)) != 0 {
t.Error("Degree returns incorrect number for impassable tile (0,0)")
}
}
func TestTileGraphCoverage(t *testing.T) {
concrete.GenerateTileGraph("▀ x") // x invalid
concrete.GenerateTileGraph("▀ \n▀") // row lengths not equal
concrete.GenerateTileGraph("▀ ▀")
tg := concrete.NewTileGraph(2, 3, true)
tg.SetPassability(0, -1, true)
tg.SetPassability(0, 1, false)
tg.SetPassability(0, 4, false)
tg.PathString(nil)
n1 := concrete.GonumNode(1)
n2 := concrete.GonumNode(2)
n3 := concrete.GonumNode(3)
n5 := concrete.GonumNode(5)
p := []gr.Node{n3, n2, n1, n5}
tg.PathString(p)
tg.Dimensions()
tg.IDToCoords(0)
tg.CoordsToNode(-1, 0)
tg.CoordsToNode(0, 0)
tg.Neighbors(n1)
tg.Neighbors(n2)
tg.IsNeighbor(n1, n2)
tg.NodeExists(n1)
tg.Degree(n1)
tg.EdgeList()
tg.NodeList()
tg.IsDirected()
tg.Cost(n1, n2)
tg.Cost(n2, n5)
}

115
graph.go
View File

@@ -15,51 +15,50 @@ type Edge interface {
Tail() Node
}
// A Graph ensures the behavior of an undirected graph, necessary to run certain algorithms on it.
// A Graph implements the behavior of an undirected graph.
//
// The Graph interface is directed. This means that EdgeList() should return an edge where Head
// always goes towards Tail. If your graph is undirected and you only maintain edges for one
// direction, simply return two edges for each one of your edges, with the Head and Tail swapped
// in each one.
// 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 Node) bool
// Degree is equivalent to len(Successors(node)) + len(Predecessors(node)).
// This means that reflexive edges are counted twice.
Degree(node Node) int
// 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) []Node
// IsNeighbor returns true when neighbor is connected to node by an edge.
IsNeighbor(node, neighbor Node) bool
// 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 UndirectedGraph
// because it can be useful to know all neighbors regardless of direction; not because this graph
// treats directed graphs as special cases of undirected ones (the truth is, in fact, the opposite.)
// 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) []Node
// IsSuccessor returns true if successor shows up in the list returned by
// Successors(node). If node doesn't exist, this should always return false.
IsSuccessor(node, successor Node) bool
// 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) []Node
// IsPredecessor returns true if predecessor shows up in the list returned
// by Predecessors(node). If node doesn't exist, this should always return
// false.
IsPredecessor(node, predecessor Node) bool
}
// Returns all undirected edges in the graph
@@ -72,6 +71,7 @@ type EdgeListGraph interface {
EdgeLister
}
// Returns all directed edges in the graph.
type DirectedEdgeLister interface {
DirectedEdgeList() []Edge
}
@@ -82,9 +82,11 @@ type DirectedEdgeListGraph interface {
}
// 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].
// [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 should have the first ID at 0.
// All dense graphs must have the first ID as 0.
type CrunchGraph interface {
Graph
Crunch()
@@ -95,10 +97,9 @@ type CrunchGraph interface {
// this function will take precedence over the Uniform Cost function (all weights are 1) if "nil"
// is passed in for the function argument.
//
// If no edge exists between node1 and node2, the cost should be taken to be +inf (can be gotten
// by math.Inf(1).)
// If the argument is nil, or the edge is invalid for some reason, this should return math.Inf(1)
type Coster interface {
Cost(node1, node2 Node) float64
Cost(edge Edge) float64
}
// Guarantees that something implementing Coster is also a Graph.
@@ -117,7 +118,7 @@ type HeuristicCoster interface {
HeuristicCost(node1, node2 Node) float64
}
// A Mutable Graph is a graph that can be changed in an arbitrary way. It is useful for several
// A MutableGraph is a graph that can be changed in an arbitrary way. It is useful for several
// algorithms; for instance, Johnson's Algorithm requires adding a temporary node and changing
// edge weights. Another case where this is used is computing minimum spanning trees. Since trees
// are graphs, a minimum spanning tree can be created using this interface.
@@ -128,41 +129,42 @@ type HeuristicCoster interface {
// properly handle the graph in order to, say, fill it with a minimum spanning tree.
//
// In functions that take a MutableGraph as an argument, it should not be the same as the Graph
// argument as concurrent modification will likely cause problems in most cases.
// argument as concurrent modification will likely cause problems.
//
// Mutable graphs should always record the IDs as they are represented -- which means they are
// MutableGraphs should always record the IDs as they are represented -- which means they are
// sparse by nature.
//
// MutableGraphs are required to keep the exact Nodes and Edges passed in, and return
// the originals when asked.
type MutableGraph interface {
CostGraph
// NewNode adds a node with an arbitrary ID and returns the new, unique ID
// used.
NewNode(successors []Node) Node
// The graph itself is responsible for adding reciprocal edges if it's
// undirected. Likewise, the graph itself must add any non-existant nodes
// listed in successors.
AddNode(node Node, successors []Node)
// For a digraph, adds node1->node2; the graph is free to initialize this
// to any value it wishes. Node1 must exist, or it will result in undefined
// behavior. Node2 must be created by the function if absent.
AddEdge(e Edge)
// The behavior is undefined if the edge has not been created with AddEdge
// (or the edge was removed before this function was called). For a
// directed graph only sets node1->node2.
SetEdgeCost(e Edge, cost float64)
// The graph is reponsible for removing edges to a node that is removed.
NewNode() Node
// Adds a node to the graph
AddNode(node Node)
// AddEdge connects two nodes in the graph. Neither node is required
// to have been added before this is called. If directed is false,
// it also adds the reciprocal edge. If this is called a second time,
// it overrides any existing edge.
AddEdge(e Edge, cost float64, directed bool)
// RemoveNode removes a node from the graph, as well as any edges
// attached to it
RemoveNode(node Node)
// The graph is responsible for removing reciprocal edges if it's
// undirected.
RemoveEdge(e Edge)
// RemoveEdge removes a connection between two nodes, but does not
// remove Head nor Tail under any circumstance. As with AddEdge, if
// directed is false it also removes the reciprocal edge. This function
// should be treated as a no-op and not an error if the edge doesn't exist.
RemoveEdge(e Edge, directed bool)
// EmptyGraph clears the graph of all nodes and edges.
EmptyGraph()
// This package will only call SetDirected on an empty graph, so there's no
// need to worry about the case where a graph suddenly becomes (un)directed.
SetDirected(bool)
}
// TODO AddNode, AddEdge, SetEdgeCost, RemoveNode, RemoveEdge, SetDirected need to say what they do.
// A DStarGraph is a special interface that allows the DStarLite function to be used on a graph.
//
// D*-lite is an algorithm that allows for the graph representation to change when actions are
@@ -185,5 +187,14 @@ type DStarGraph interface {
ChangedEdges() (newCostFunc func(Node, Node) float64, changedEdges []Edge)
}
// A function that returns the cost from one node to another.
type CostFunc func(Node, Node) float64
// 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
// Convenience constants for AddEdge and RemoveEdge
const (
Directed bool = true
Undirected = false
)

View File

@@ -11,7 +11,7 @@ func TestFWOneEdge(t *testing.T) {
dg := concrete.NewDenseGraph(2, true)
aPaths, sPath := search.FloydWarshall(dg, nil)
path, cost, err := sPath(concrete.GonumNode(0), concrete.GonumNode(1))
path, cost, err := sPath(concrete.Node(0), concrete.Node(1))
if err != nil {
t.Fatal(err)
}
@@ -24,7 +24,7 @@ func TestFWOneEdge(t *testing.T) {
t.Errorf("Wrong path in FW %v", path)
}
paths, cost, err := aPaths(concrete.GonumNode(0), concrete.GonumNode(1))
paths, cost, err := aPaths(concrete.Node(0), concrete.Node(1))
if err != nil {
t.Fatal(err)
}
@@ -46,12 +46,12 @@ func TestFWOneEdge(t *testing.T) {
func TestFWTwoPaths(t *testing.T) {
dg := concrete.NewDenseGraph(5, false)
// Adds two paths from 0->2 of equal length
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(2), 2.0, true)
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(1), 1.0, true)
dg.SetEdgeCost(concrete.GonumNode(1), concrete.GonumNode(2), 1.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1.0, true)
aPaths, sPath := search.FloydWarshall(dg, nil)
path, cost, err := sPath(concrete.GonumNode(0), concrete.GonumNode(2))
path, cost, err := sPath(concrete.Node(0), concrete.Node(2))
if err != nil {
t.Fatal(err)
}
@@ -68,7 +68,7 @@ func TestFWTwoPaths(t *testing.T) {
t.Errorf("Got wrong path %v", path)
}
paths, cost, err := aPaths(concrete.GonumNode(0), concrete.GonumNode(2))
paths, cost, err := aPaths(concrete.Node(0), concrete.Node(2))
if err != nil {
t.Fatal(err)
@@ -99,26 +99,26 @@ func TestFWConfoundingPath(t *testing.T) {
dg := concrete.NewDenseGraph(6, false)
// Add a path from 0->5 of cost 4
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(1), 1.0, true)
dg.SetEdgeCost(concrete.GonumNode(1), concrete.GonumNode(2), 1.0, true)
dg.SetEdgeCost(concrete.GonumNode(2), concrete.GonumNode(3), 1.0, true)
dg.SetEdgeCost(concrete.GonumNode(3), concrete.GonumNode(5), 1.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(3), concrete.Node(5)}, 1.0, true)
// Add direct edge to goal of cost 4
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(5), 4.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(5)}, 4.0, true)
// Add edge to a node that's still optimal
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(2), 2.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2.0, true)
// Add edge to 3 that's overpriced
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(3), 4.0, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(3)}, 4.0, true)
// Add very cheap edge to 4 which is a dead end
dg.SetEdgeCost(concrete.GonumNode(0), concrete.GonumNode(4), 0.25, true)
dg.SetEdgeCost(concrete.Edge{concrete.Node(0), concrete.Node(4)}, 0.25, true)
aPaths, sPath := search.FloydWarshall(dg, nil)
path, cost, err := sPath(concrete.GonumNode(0), concrete.GonumNode(5))
path, cost, err := sPath(concrete.Node(0), concrete.Node(5))
if err != nil {
t.Fatal(err)
}
@@ -137,7 +137,7 @@ func TestFWConfoundingPath(t *testing.T) {
t.Errorf("Wrong path found for single path %v", path)
}
paths, cost, err := aPaths(concrete.GonumNode(0), concrete.GonumNode(5))
paths, cost, err := aPaths(concrete.Node(0), concrete.Node(5))
if err != nil {
t.Fatal(err)
}
@@ -162,14 +162,14 @@ func TestFWConfoundingPath(t *testing.T) {
}
}
path, _, err = sPath(concrete.GonumNode(4), concrete.GonumNode(5))
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.GonumNode(4), concrete.GonumNode(5))
paths, _, err = aPaths(concrete.Node(4), concrete.Node(5))
if err != nil {
t.Log("Success!", err)
} else {

View File

@@ -5,14 +5,14 @@ import (
"math"
"sort"
gr "github.com/gonum/graph"
"github.com/gonum/graph"
)
// Finds all shortest paths between start and goal
type AllPathFunc func(start, goal gr.Node) (path [][]gr.Node, cost float64, err error)
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 gr.Node) (path []gr.Node, cost float64, err error)
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.
@@ -32,12 +32,12 @@ type PathFunc func(start, goal gr.Node) (path []gr.Node, cost float64, err error
// 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(graph gr.CrunchGraph, cost gr.CostFunc) (AllPathFunc, PathFunc) {
graph.Crunch()
sf := setupFuncs(graph, cost, nil)
successors, isSuccessor, cost := sf.successors, sf.isSuccessor, sf.cost
func FloydWarshall(gr graph.CrunchGraph, cost graph.CostFunc) (AllPathFunc, PathFunc) {
gr.Crunch()
sf := setupFuncs(gr, cost, nil)
successors, isSuccessor, cost, edgeTo := sf.successors, sf.isSuccessor, sf.cost, sf.edgeTo
nodes := denseNodeSorter(graph.NodeList())
nodes := denseNodeSorter(gr.NodeList())
sort.Sort(nodes)
numNodes := len(nodes)
@@ -53,7 +53,7 @@ func FloydWarshall(graph gr.CrunchGraph, cost gr.CostFunc) (AllPathFunc, PathFun
for _, node := range nodes {
for _, succ := range successors(node) {
dist[node.ID()+succ.ID()*numNodes] = cost(node, succ)
dist[node.ID()+succ.ID()*numNodes] = cost(edgeTo(node, succ))
}
}
@@ -80,29 +80,29 @@ func FloydWarshall(graph gr.CrunchGraph, cost gr.CostFunc) (AllPathFunc, PathFun
}
}
return genAllPathsFunc(dist, next, nodes, graph, cost, isSuccessor), genSinglePathFunc(dist, next, nodes)
return genAllPathsFunc(dist, next, nodes, gr, cost, isSuccessor, edgeTo), genSinglePathFunc(dist, next, nodes)
}
func genAllPathsFunc(dist []float64, next [][]int, nodes []gr.Node, graph gr.Graph, cost func(gr.Node, gr.Node) float64, isSuccessor func(gr.Node, gr.Node) bool) func(start, goal gr.Node) ([][]gr.Node, float64, error) {
func genAllPathsFunc(dist []float64, next [][]int, nodes []graph.Node, gr 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) ([][]gr.Node, error)
allPathFinder = func(i, j int) ([][]gr.Node, error) {
var allPathFinder func(i, j int) ([][]graph.Node, error)
allPathFinder = func(i, j int) ([][]graph.Node, error) {
if dist[i+j*numNodes] == math.Inf(1) {
return nil, errors.New("No path")
}
intermediates := next[i+j*numNodes]
if intermediates == nil || len(intermediates) == 0 {
// There is exactly one path
return [][]gr.Node{[]gr.Node{}}, nil
return [][]graph.Node{[]graph.Node{}}, nil
}
toReturn := make([][]gr.Node, 0, len(intermediates))
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(nodes[i], nodes[j])) < .000001 {
toReturn = append(toReturn, []gr.Node{})
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.
@@ -139,7 +139,7 @@ func genAllPathsFunc(dist []float64, next [][]int, nodes []gr.Node, graph gr.Gra
// (the copying stuff is because slices are reference types)
for a := range succs {
for b := range preds {
path := make([]gr.Node, len(succs[a]), len(succs[a])+len(preds[b]))
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)
@@ -151,7 +151,7 @@ func genAllPathsFunc(dist []float64, next [][]int, nodes []gr.Node, graph gr.Gra
return toReturn, nil
}
return func(start, goal gr.Node) ([][]gr.Node, float64, error) {
return func(start, goal graph.Node) ([][]graph.Node, float64, error) {
paths, err := allPathFinder(start.ID(), goal.ID())
if err != nil {
return nil, math.Inf(1), err
@@ -179,18 +179,18 @@ func genAllPathsFunc(dist []float64, next [][]int, nodes []gr.Node, graph gr.Gra
}
}
func genSinglePathFunc(dist []float64, next [][]int, nodes []gr.Node) func(start, goal gr.Node) ([]gr.Node, float64, error) {
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) ([]gr.Node, error)
singlePathFinder = func(i, j int) ([]gr.Node, error) {
var singlePathFinder func(i, j int) ([]graph.Node, error)
singlePathFinder = func(i, j int) ([]graph.Node, error) {
if dist[i+j*numNodes] == math.Inf(1) {
return nil, errors.New("No path")
}
intermediates := next[i+j*numNodes]
if intermediates == nil || len(intermediates) == 0 {
return []gr.Node{}, nil
return []graph.Node{}, nil
}
intermediate := intermediates[0]
@@ -209,7 +209,7 @@ func genSinglePathFunc(dist []float64, next [][]int, nodes []gr.Node) func(start
return path, nil
}
return func(start, goal gr.Node) ([]gr.Node, float64, error) {
return func(start, goal graph.Node) ([]graph.Node, float64, error) {
path, err := singlePathFinder(start.ID(), goal.ID())
if err != nil {
return nil, math.Inf(1), err

View File

@@ -6,7 +6,7 @@ import (
"sort"
"errors"
gr "github.com/gonum/graph"
"github.com/gonum/graph"
"github.com/gonum/graph/concrete"
"github.com/gonum/graph/set"
"github.com/gonum/graph/xifo"
@@ -40,16 +40,16 @@ import (
//
// 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 gr.Node, graph gr.Graph, cost, heuristicCost gr.CostFunc) (path []gr.Node, pathCost float64, nodesExpanded int) {
sf := setupFuncs(graph, cost, heuristicCost)
successors, cost, heuristicCost := sf.successors, sf.cost, sf.heuristicCost
func AStar(start, goal graph.Node, gr graph.Graph, cost graph.CostFunc, heuristicCost graph.HeuristicCostFunc) (path []graph.Node, pathCost float64, nodesExpanded int) {
sf := setupFuncs(gr, 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]gr.Node)
predecessor := make(map[int]graph.Node)
for openSet.Len() != 0 {
curr := heap.Pop(openSet).(internalNode)
@@ -67,7 +67,7 @@ func AStar(start, goal gr.Node, graph gr.Graph, cost, heuristicCost gr.CostFunc)
continue
}
g := curr.gscore + cost(curr.Node, neighbor)
g := curr.gscore + cost(edgeTo(curr.Node, neighbor))
if existing, exists := openSet.Find(neighbor.ID()); !exists {
predecessor[neighbor.ID()] = curr
@@ -87,8 +87,8 @@ func AStar(start, goal gr.Node, graph gr.Graph, cost, heuristicCost gr.CostFunc)
//
// 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 gr.Node, graph gr.Graph) ([]gr.Node, int) {
path, _, visited := AStar(start, goal, graph, UniformCost, NullHeuristic)
func BreadthFirstSearch(start, goal graph.Node, gr graph.Graph) ([]graph.Node, int) {
path, _, visited := AStar(start, goal, gr, UniformCost, NullHeuristic)
return path, visited
}
@@ -104,17 +104,17 @@ func BreadthFirstSearch(start, goal gr.Node, graph gr.Graph) ([]gr.Node, int) {
//
// 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 gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][]gr.Node, costs map[int]float64) {
func Dijkstra(source graph.Node, gr graph.Graph, cost graph.CostFunc) (paths map[int][]graph.Node, costs map[int]float64) {
sf := setupFuncs(graph, cost, nil)
successors, cost := sf.successors, sf.cost
sf := setupFuncs(gr, cost, nil)
successors, cost, edgeTo := sf.successors, sf.cost, sf.edgeTo
nodes := graph.NodeList()
nodes := gr.NodeList()
openSet := &aStarPriorityQueue{nodes: make([]internalNode, 0), indexList: make(map[int]int)}
closedSet := set.NewSet() // This is to make use of that same
costs = make(map[int]float64, len(nodes)) // May overallocate, will change if it becomes a problem
predecessor := make(map[int]gr.Node, len(nodes))
nodeIDMap := make(map[int]gr.Node, len(nodes))
predecessor := make(map[int]graph.Node, len(nodes))
nodeIDMap := make(map[int]graph.Node, len(nodes))
heap.Init(openSet)
costs[source.ID()] = 0
@@ -128,7 +128,7 @@ func Dijkstra(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][
closedSet.Add(node.ID())
for _, neighbor := range successors(node) {
tmpCost := costs[node.ID()] + cost(node, neighbor)
tmpCost := costs[node.ID()] + cost(edgeTo(node, neighbor))
if cost, ok := costs[neighbor.ID()]; !ok {
costs[neighbor.ID()] = tmpCost
predecessor[neighbor.ID()] = node
@@ -141,7 +141,7 @@ func Dijkstra(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][
}
}
paths = make(map[int][]gr.Node, len(costs))
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])
}
@@ -162,23 +162,23 @@ func Dijkstra(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][
// 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 gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[int][]gr.Node, costs map[int]float64, err error) {
sf := setupFuncs(graph, cost, nil)
successors, cost := sf.successors, sf.cost
func BellmanFord(source graph.Node, gr graph.Graph, cost graph.CostFunc) (paths map[int][]graph.Node, costs map[int]float64, err error) {
sf := setupFuncs(gr, cost, nil)
successors, cost, edgeTo := sf.successors, sf.cost, sf.edgeTo
predecessor := make(map[int]gr.Node)
predecessor := make(map[int]graph.Node)
costs = make(map[int]float64)
nodeIDMap := make(map[int]gr.Node)
nodeIDMap := make(map[int]graph.Node)
nodeIDMap[source.ID()] = source
costs[source.ID()] = 0
nodes := graph.NodeList()
nodes := gr.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(node, succ)
weight := cost(edgeTo(node, succ))
nodeIDMap[succ.ID()] = succ
if dist := costs[node.ID()] + weight; dist < costs[succ.ID()] {
@@ -192,14 +192,14 @@ func BellmanFord(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[in
for _, node := range nodes {
for _, succ := range successors(node) {
weight := cost(node, succ)
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][]gr.Node, len(costs))
paths = make(map[int][]graph.Node, len(costs))
for node, _ := range costs {
paths[node] = rebuildPath(predecessor, nodeIDMap[node])
}
@@ -223,31 +223,26 @@ func BellmanFord(source gr.Node, graph gr.Graph, cost gr.CostFunc) (paths map[in
// 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(graph gr.Graph, cost gr.CostFunc) (nodePaths map[int]map[int][]gr.Node, nodeCosts map[int]map[int]float64, err error) {
sf := setupFuncs(graph, cost, nil)
successors, cost := sf.successors, sf.cost
func Johnson(gr graph.Graph, cost graph.CostFunc) (nodePaths map[int]map[int][]graph.Node, nodeCosts map[int]map[int]float64, err error) {
sf := setupFuncs(gr, 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.NewGonumGraph(true)
for _, node := range graph.NodeList() {
dummyGraph := concrete.NewGraph()
for _, node := range gr.NodeList() {
neighbors := successors(node)
if !dummyGraph.NodeExists(node) {
dummyGraph.AddNode(node, neighbors)
for _, neighbor := range neighbors {
dummyGraph.SetEdgeCost(concrete.GonumEdge{node, neighbor}, cost(node, neighbor))
}
} else {
for _, neighbor := range neighbors {
dummyGraph.AddEdge(concrete.GonumEdge{node, neighbor})
dummyGraph.SetEdgeCost(concrete.GonumEdge{node, neighbor}, cost(node, neighbor))
}
dummyGraph.NodeExists(node)
dummyGraph.AddNode(node)
for _, neighbor := range neighbors {
e := edgeTo(node, neighbor)
dummyGraph.AddEdge(e, cost(e), true)
}
}
/* Step 1: Dummy node with 0 cost edge weights to every other node*/
dummyNode := dummyGraph.NewNode(graph.NodeList())
for _, node := range graph.NodeList() {
dummyGraph.SetEdgeCost(concrete.GonumEdge{dummyNode, node}, 0)
dummyNode := dummyGraph.NewNode()
for _, node := range gr.NodeList() {
dummyGraph.AddEdge(concrete.Edge{dummyNode, node}, 0.0, true)
}
/* Step 2: Run Bellman-Ford starting at the dummy node, abort if it detects a cycle */
@@ -257,19 +252,20 @@ func Johnson(graph gr.Graph, cost gr.CostFunc) (nodePaths map[int]map[int][]gr.N
}
/* Step 3: reweight the graph and remove the dummy node */
for _, node := range graph.NodeList() {
for _, node := range gr.NodeList() {
for _, succ := range successors(node) {
dummyGraph.SetEdgeCost(concrete.GonumEdge{node, succ}, cost(node, succ)+costs[node.ID()]-costs[succ.ID()])
e := edgeTo(node, succ)
dummyGraph.AddEdge(e, cost(e)+costs[node.ID()]-costs[succ.ID()], true)
}
}
dummyGraph.RemoveNode(dummyNode)
/* Step 4: Run Dijkstra's starting at every node */
nodePaths = make(map[int]map[int][]gr.Node, len(graph.NodeList()))
nodePaths = make(map[int]map[int][]graph.Node, len(gr.NodeList()))
nodeCosts = make(map[int]map[int]float64)
for _, node := range graph.NodeList() {
for _, node := range gr.NodeList() {
nodePaths[node.ID()], nodeCosts[node.ID()] = Dijkstra(node, dummyGraph, nil)
}
@@ -279,18 +275,18 @@ func Johnson(graph gr.Graph, cost gr.CostFunc) (nodePaths map[int]map[int][]gr.N
// 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 gr.Node, graph gr.Graph) []gr.Node {
sf := setupFuncs(graph, nil, nil)
func DepthFirstSearch(start, goal graph.Node, gr graph.Graph) []graph.Node {
sf := setupFuncs(gr, nil, nil)
successors := sf.successors
closedSet := set.NewSet()
openSet := xifo.GonumStack([]interface{}{start})
predecessor := make(map[int]gr.Node)
predecessor := make(map[int]graph.Node)
for !openSet.IsEmpty() {
c := openSet.Pop()
curr := c.(gr.Node)
curr := c.(graph.Node)
if closedSet.Contains(curr.ID()) {
continue
@@ -316,35 +312,34 @@ func DepthFirstSearch(start, goal gr.Node, graph gr.Graph) []gr.Node {
}
// An admissible, consistent heuristic that won't speed up computation time at all.
func NullHeuristic(a, b gr.Node) float64 {
func NullHeuristic(node1, node2 graph.Node) float64 {
return 0.0
}
// Assumes all edges in the graph have the same weight (including edges that don't exist!)
func UniformCost(a, b gr.Node) float64 {
func UniformCost(e graph.Edge) float64 {
if e == nil {
return math.Inf(1)
}
return 1.0
}
/* Simple operations */
// Copies a graph into the destination; maintaining all node IDs.
func CopyGraph(dst gr.MutableGraph, src gr.Graph) {
func CopyGraph(dst graph.MutableGraph, src graph.Graph) {
dst.EmptyGraph()
dst.SetDirected(false)
sf := setupFuncs(src, nil, nil)
successors, cost := sf.successors, sf.cost
successors, cost, edgeTo := sf.successors, sf.cost, sf.edgeTo
for _, node := range src.NodeList() {
succs := successors(node)
if !dst.NodeExists(node) {
dst.AddNode(node, succs)
} else {
for _, succ := range succs {
edge := concrete.GonumEdge{node, succ}
dst.AddEdge(edge)
dst.SetEdgeCost(edge, cost(node, succ))
}
dst.AddNode(node)
for _, succ := range succs {
edge := edgeTo(node, succ)
dst.AddEdge(edge, cost(edge), true)
}
}
@@ -365,21 +360,21 @@ func CopyGraph(dst gr.MutableGraph, src gr.Graph) {
// 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(graph gr.Graph) (sccs [][]gr.Node) {
func Tarjan(gr graph.Graph) (sccs [][]graph.Node) {
index := 0
vStack := &xifo.GonumStack{}
stackSet := set.NewSet()
sccs = make([][]gr.Node, 0)
sccs = make([][]graph.Node, 0)
nodes := graph.NodeList()
nodes := gr.NodeList()
lowlinks := make(map[int]int, len(nodes))
indices := make(map[int]int, len(nodes))
successors := setupFuncs(graph, nil, nil).successors
successors := setupFuncs(gr, nil, nil).successors
var strongconnect func(gr.Node) []gr.Node
var strongconnect func(graph.Node) []graph.Node
strongconnect = func(node gr.Node) []gr.Node {
strongconnect = func(node graph.Node) []graph.Node {
indices[node.ID()] = index
lowlinks[node.ID()] = index
index += 1
@@ -397,12 +392,12 @@ func Tarjan(graph gr.Graph) (sccs [][]gr.Node) {
}
if lowlinks[node.ID()] == indices[node.ID()] {
scc := make([]gr.Node, 0)
scc := make([]graph.Node, 0)
for {
v := vStack.Pop()
stackSet.Remove(v.(gr.Node).ID())
scc = append(scc, v.(gr.Node))
if v.(gr.Node).ID() == node.ID() {
stackSet.Remove(v.(graph.Node).ID())
scc = append(scc, v.(graph.Node))
if v.(graph.Node).ID() == node.ID() {
return scc
}
}
@@ -429,13 +424,13 @@ func Tarjan(graph gr.Graph) (sccs [][]gr.Node) {
// (only one node) but only if the node listed in path exists within the graph.
//
// Graph must be non-nil.
func IsPath(path []gr.Node, graph gr.Graph) bool {
isSuccessor := setupFuncs(graph, nil, nil).isSuccessor
func IsPath(path []graph.Node, gr graph.Graph) bool {
isSuccessor := setupFuncs(gr, nil, nil).isSuccessor
if path == nil || len(path) == 0 {
return true
} else if len(path) == 1 {
return graph.NodeExists(path[0])
return gr.NodeExists(path[0])
}
for i := 0; i < len(path)-1; i++ {
@@ -454,46 +449,39 @@ puts the resulting minimum spanning tree in the dst graph */
//
// As with other algorithms that use Cost, the order of precedence is
// Argument > Interface > UniformCost.
func Prim(dst gr.MutableGraph, graph gr.EdgeListGraph, cost gr.CostFunc) {
cost = setupFuncs(graph, cost, nil).cost
func Prim(dst graph.MutableGraph, gr graph.EdgeListGraph, cost graph.CostFunc) {
sf := setupFuncs(gr, cost, nil)
cost = sf.cost
dst.EmptyGraph()
dst.SetDirected(false)
nlist := graph.NodeList()
nlist := gr.NodeList()
if nlist == nil || len(nlist) == 0 {
return
}
dst.AddNode(nlist[0], nil)
dst.AddNode(nlist[0])
remainingNodes := set.NewSet()
for _, node := range nlist[1:] {
remainingNodes.Add(node.ID())
}
edgeList := graph.EdgeList()
edgeList := gr.EdgeList()
for remainingNodes.Cardinality() != 0 {
edgeWeights := make(edgeSorter, 0)
for _, edge := range edgeList {
if dst.NodeExists(edge.Head()) && remainingNodes.Contains(edge.Tail().ID()) {
edgeWeights = append(edgeWeights, WeightedEdge{Edge: edge, Weight: cost(edge.Head(), edge.Tail())})
} else if dst.NodeExists(edge.Tail()) && remainingNodes.Contains(edge.Head().ID()) {
edgeWeights = append(edgeWeights, WeightedEdge{Edge: edge, Weight: cost(edge.Tail(), edge.Head())})
if (dst.NodeExists(edge.Head()) && remainingNodes.Contains(edge.Tail().ID())) ||
(dst.NodeExists(edge.Tail()) && remainingNodes.Contains(edge.Head().ID())) {
edgeWeights = append(edgeWeights, concrete.WeightedEdge{Edge: edge, Cost: cost(edge)})
}
}
sort.Sort(edgeWeights)
myEdge := edgeWeights[0]
// Since it's undirected this doesn't need to check head vs tail
if !dst.NodeExists(myEdge.Head()) {
dst.AddNode(myEdge.Head(), []gr.Node{myEdge.Tail()})
} else {
dst.AddEdge(myEdge.Edge)
}
dst.SetEdgeCost(myEdge.Edge, myEdge.Weight)
dst.AddEdge(myEdge.Edge, myEdge.Cost, false)
remainingNodes.Remove(myEdge.Edge.Head())
}
@@ -502,22 +490,20 @@ func Prim(dst gr.MutableGraph, graph gr.EdgeListGraph, cost gr.CostFunc) {
// Generates a minimum spanning tree for a graph using discrete.DisjointSet.
//
// As with other algorithms with Cost, the precedence goes Argument > Interface > UniformCost.
func Kruskal(dst gr.MutableGraph, graph gr.EdgeListGraph, cost func(gr.Node, gr.Node) float64) {
cost = setupFuncs(graph, cost, nil).cost
func Kruskal(dst graph.MutableGraph, gr graph.EdgeListGraph, cost graph.CostFunc) {
cost = setupFuncs(gr, cost, nil).cost
dst.EmptyGraph()
dst.SetDirected(false)
edgeList := graph.EdgeList()
edgeList := gr.EdgeList()
edgeWeights := make(edgeSorter, 0, len(edgeList))
for _, edge := range edgeList {
edgeWeights = append(edgeWeights, WeightedEdge{Edge: edge, Weight: cost(edge.Head(), edge.Tail())})
edgeWeights = append(edgeWeights, concrete.WeightedEdge{Edge: edge, Cost: cost(edge)})
}
sort.Sort(edgeWeights)
ds := set.NewDisjointSet()
for _, node := range graph.NodeList() {
for _, node := range gr.NodeList() {
ds.MakeSet(node.ID())
}
@@ -526,12 +512,7 @@ func Kruskal(dst gr.MutableGraph, graph gr.EdgeListGraph, cost func(gr.Node, gr.
// 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)
if !dst.NodeExists(edge.Edge.Head()) {
dst.AddNode(edge.Edge.Head(), []gr.Node{edge.Edge.Tail()})
} else {
dst.AddEdge(edge.Edge)
}
dst.SetEdgeCost(edge.Edge, edge.Weight)
dst.AddEdge(edge.Edge, edge.Cost, false)
}
}
}
@@ -544,15 +525,15 @@ func Kruskal(dst gr.MutableGraph, graph gr.EdgeListGraph, cost func(gr.Node, gr.
// immediate dominators etc.
//
// The int map[int]*set.Set is the node's ID.
func Dominators(start gr.Node, graph gr.Graph) map[int]*set.Set {
func Dominators(start graph.Node, gr graph.Graph) map[int]*set.Set {
allNodes := set.NewSet()
nlist := graph.NodeList()
nlist := gr.NodeList()
dominators := make(map[int]*set.Set, len(nlist))
for _, node := range nlist {
allNodes.Add(node.ID())
}
predecessors := setupFuncs(graph, nil, nil).predecessors
predecessors := setupFuncs(gr, nil, nil).predecessors
for _, node := range nlist {
dominators[node.ID()] = set.NewSet()
@@ -596,11 +577,11 @@ func Dominators(start gr.Node, graph gr.Graph) map[int]*set.Set {
//
// This returns all possible post-dominators for all nodes, it does not prune for strict
// postdominators, immediate postdominators etc.
func PostDominators(end gr.Node, graph gr.Graph) map[int]*set.Set {
successors := setupFuncs(graph, nil, nil).successors
func PostDominators(end graph.Node, gr graph.Graph) map[int]*set.Set {
successors := setupFuncs(gr, nil, nil).successors
allNodes := set.NewSet()
nlist := graph.NodeList()
nlist := gr.NodeList()
dominators := make(map[int]*set.Set, len(nlist))
for _, node := range nlist {
allNodes.Add(node.ID())

View File

@@ -3,43 +3,69 @@ package search
import (
"container/heap"
gr "github.com/gonum/graph"
"github.com/gonum/graph"
"github.com/gonum/graph/concrete"
)
type searchFuncs struct {
successors, predecessors, neighbors func(gr.Node) []gr.Node
isSuccessor, isPredecessor, isNeighbor func(gr.Node, gr.Node) bool
cost, heuristicCost gr.CostFunc
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(gr graph.DirectedGraph) func(graph.Node, graph.Node) bool {
return func(node, succ graph.Node) bool {
return gr.EdgeTo(node, succ) != nil
}
}
func genIsPredecessor(gr graph.DirectedGraph) func(graph.Node, graph.Node) bool {
return func(node, succ graph.Node) bool {
return gr.EdgeTo(succ, node) != nil
}
}
func genIsNeighbor(gr graph.Graph) func(graph.Node, graph.Node) bool {
return func(node, succ graph.Node) bool {
return gr.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(graph gr.Graph, cost, heuristicCost gr.CostFunc) searchFuncs {
func setupFuncs(gr graph.Graph, cost graph.CostFunc, heuristicCost graph.HeuristicCostFunc) searchFuncs {
sf := searchFuncs{}
switch g := graph.(type) {
case gr.DirectedGraph:
switch g := gr.(type) {
case graph.DirectedGraph:
sf.successors = g.Successors
sf.predecessors = g.Predecessors
sf.neighbors = g.Neighbors
sf.isSuccessor = g.IsSuccessor
sf.isPredecessor = g.IsPredecessor
sf.isNeighbor = g.IsNeighbor
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
sf.isSuccessor = g.IsNeighbor
sf.isPredecessor = g.IsNeighbor
sf.isNeighbor = g.IsNeighbor
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 := graph.(gr.HeuristicCoster); ok {
if g, ok := gr.(graph.HeuristicCoster); ok {
sf.heuristicCost = g.HeuristicCost
} else {
sf.heuristicCost = NullHeuristic
@@ -49,7 +75,7 @@ func setupFuncs(graph gr.Graph, cost, heuristicCost gr.CostFunc) searchFuncs {
if cost != nil {
sf.cost = cost
} else {
if g, ok := graph.(gr.Coster); ok {
if g, ok := gr.(graph.Coster); ok {
sf.cost = g.Cost
} else {
sf.cost = UniformCost
@@ -59,25 +85,16 @@ func setupFuncs(graph gr.Graph, cost, heuristicCost gr.CostFunc) searchFuncs {
return sf
}
/* Purely internal data structures and functions (mostly for sorting) */
// A package that contains an edge (as from EdgeList), and a Weight (as if Cost(Edge.Head(),
// Edge.Tail()) had been called.)
type WeightedEdge struct {
gr.Edge
Weight float64
}
/** Sorts a list of edges by weight, agnostic to repeated edges as well as direction **/
type edgeSorter []WeightedEdge
type edgeSorter []concrete.WeightedEdge
func (el edgeSorter) Len() int {
return len(el)
}
func (el edgeSorter) Less(i, j int) bool {
return el[i].Weight < el[j].Weight
return el[i].Cost < el[j].Cost
}
func (el edgeSorter) Swap(i, j int) {
@@ -87,7 +104,7 @@ func (el edgeSorter) Swap(i, j int) {
/** Keeps track of a node's scores so they can be used in a priority queue for A* **/
type internalNode struct {
gr.Node
graph.Node
gscore, fscore float64
}
@@ -151,7 +168,7 @@ func (pq *aStarPriorityQueue) Exists(id int) bool {
return ok
}
type denseNodeSorter []gr.Node
type denseNodeSorter []graph.Node
func (dns denseNodeSorter) Less(i, j int) bool {
return dns[i].ID() < dns[j].ID()
@@ -168,11 +185,11 @@ func (dns denseNodeSorter) Len() int {
// General utility funcs
// Rebuilds a path backwards from the goal.
func rebuildPath(predecessors map[int]gr.Node, goal gr.Node) []gr.Node {
func rebuildPath(predecessors map[int]graph.Node, goal graph.Node) []graph.Node {
if n, ok := goal.(internalNode); ok {
goal = n.Node
}
path := []gr.Node{goal}
path := []graph.Node{goal}
curr := goal
for prev, ok := predecessors[curr.ID()]; ok; prev, ok = predecessors[curr.ID()] {
if n, ok := prev.(internalNode); ok {

View File

@@ -16,7 +16,7 @@ func TestSimpleAStar(t *testing.T) {
t.Fatal("Couldn't generate tilegraph")
}
path, cost, _ := search.AStar(concrete.GonumNode(1), concrete.GonumNode(14), tg, nil, nil)
path, cost, _ := search.AStar(concrete.Node(1), concrete.Node(14), tg, nil, nil)
if math.Abs(cost-4.0) > .00001 {
t.Errorf("A* reports incorrect cost for simple tilegraph search")
}
@@ -39,14 +39,14 @@ func TestSimpleAStar(t *testing.T) {
func TestBiggerAStar(t *testing.T) {
tg := concrete.NewTileGraph(3, 3, true)
path, cost, _ := search.AStar(concrete.GonumNode(0), concrete.GonumNode(8), tg, nil, nil)
path, cost, _ := search.AStar(concrete.Node(0), concrete.Node(8), tg, nil, nil)
if math.Abs(cost-4.0) > .00001 || !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.GonumNode(00), concrete.GonumNode(999*1000+999), tg, nil, nil)
path, cost, _ = search.AStar(concrete.Node(00), concrete.Node(999*1000+999), tg, nil, nil)
if !search.IsPath(path, tg) || cost != 1998.0 {
t.Error("Non-optimal or impossible path found for 100x100 grid; cost:", cost, "path:\n"+tg.PathString(path))
}
@@ -67,7 +67,7 @@ func TestObstructedAStar(t *testing.T) {
tg.SetPassability(4, 9, false)
rows, cols := tg.Dimensions()
path, cost1, expanded := search.AStar(concrete.GonumNode(5), tg.CoordsToNode(rows-1, cols-1), tg, nil, nil)
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")
@@ -81,7 +81,7 @@ func TestObstructedAStar(t *testing.T) {
return math.Abs(float64(r1)-float64(r2)) + math.Abs(float64(c1)-float64(c2))
}
path, cost2, expanded2 := search.AStar(concrete.GonumNode(5), tg.CoordsToNode(rows-1, cols-1), tg, nil, ManhattanHeuristic)
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")
}
@@ -126,7 +126,7 @@ func TestSmallAStar(t *testing.T) {
// assert that AStar finds each path
for goalID, dPath := range dPaths {
exp := fmt.Sprintln(dPath, dCosts[goalID])
aPath, aCost, _ := search.AStar(start, concrete.GonumNode(goalID), gg, nil, heur)
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)
@@ -136,10 +136,11 @@ func TestSmallAStar(t *testing.T) {
}
func ExampleBreadthFirstSearch() {
g := concrete.NewGonumGraph(true)
var n0, n1, n2, n3 concrete.GonumNode = 0, 1, 2, 3
g.AddNode(n0, []graph.Node{n1, n2})
g.AddEdge(concrete.GonumEdge{n2, n3})
g := concrete.NewGraph()
var n0, n1, n2, n3 concrete.Node = 0, 1, 2, 3
g.AddEdge(concrete.Edge{n0, n1}, 1.0, true)
g.AddEdge(concrete.Edge{n0, n2}, 1.0, true)
g.AddEdge(concrete.Edge{n2, n3}, 1.0, true)
path, v := search.BreadthFirstSearch(n0, n3, g)
fmt.Println("path:", path)
fmt.Println("nodes visited:", v)
@@ -148,7 +149,7 @@ func ExampleBreadthFirstSearch() {
// nodes visited: 4
}
func newSmallGonumGraph() *concrete.GonumGraph {
func newSmallGonumGraph() *concrete.Graph {
eds := []struct{ n1, n2, edgeCost int }{
{1, 2, 7},
{1, 3, 9},
@@ -160,17 +161,16 @@ func newSmallGonumGraph() *concrete.GonumGraph {
{4, 5, 7},
{5, 6, 9},
}
g := concrete.NewGonumGraph(false)
for n := concrete.GonumNode(1); n <= 6; n++ {
g.AddNode(n, nil)
g := concrete.NewGraph()
for n := concrete.Node(1); n <= 6; n++ {
g.AddNode(n)
}
for _, ed := range eds {
e := concrete.GonumEdge{
concrete.GonumNode(ed.n1),
concrete.GonumNode(ed.n2),
e := concrete.Edge{
concrete.Node(ed.n1),
concrete.Node(ed.n2),
}
g.AddEdge(e)
g.SetEdgeCost(e, float64(ed.edgeCost))
g.AddEdge(e, float64(ed.edgeCost), false)
}
return g
}
@@ -203,7 +203,7 @@ func monotonic(g costEdgeListGraph, heur func(n1, n2 graph.Node) float64) (bool,
for _, edge := range g.EdgeList() {
head := edge.Head()
tail := edge.Tail()
if heur(head, goal) > g.Cost(head, tail)+heur(tail, goal) {
if heur(head, goal) > g.Cost(edge)+heur(tail, goal) {
return false, edge, goal
}
}
@@ -214,7 +214,7 @@ func monotonic(g costEdgeListGraph, heur func(n1, n2 graph.Node) float64) (bool,
// Test for correct result on a small graph easily solvable by hand
func TestDijkstraSmall(t *testing.T) {
g := newSmallGonumGraph()
paths, lens := search.Dijkstra(concrete.GonumNode(1), g, nil)
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])
@@ -232,39 +232,40 @@ func TestDijkstraSmall(t *testing.T) {
}
func TestIsPath(t *testing.T) {
g := concrete.NewGonumGraph(true)
if search.IsPath(nil, g) != true {
t.Error("nil path")
g := concrete.NewGraph()
if !search.IsPath(nil, g) {
t.Error("IsPath returns false on nil path")
}
p := []graph.Node{concrete.GonumNode(0)}
if search.IsPath(p, g) != false {
t.Error("node not in graph")
p := []graph.Node{concrete.Node(0)}
if search.IsPath(p, g) {
t.Error("IsPath returns true on nonexistant node")
}
g.AddNode(p[0], nil)
if search.IsPath(p, g) != true {
t.Error("single node in graph")
g.AddNode(p[0])
if !search.IsPath(p, g) {
t.Error("IsPath returns false on single-length path with existing node")
}
p = append(p, concrete.GonumNode(1))
g.AddNode(p[1], nil)
if search.IsPath(p, g) != false {
t.Error("unconnected nodes")
p = append(p, concrete.Node(1))
g.AddNode(p[1])
if search.IsPath(p, g) {
t.Error("IsPath returns true on bad path of length 2")
}
g.AddEdge(concrete.GonumEdge{p[0], p[1]})
if search.IsPath(p, g) != true {
t.Error("connected nodes")
g.AddEdge(concrete.Edge{p[0], p[1]}, 1.0, true)
if !search.IsPath(p, g) {
t.Error("IsPath returns false on correct path of length 2")
}
p[0], p[1] = p[1], p[0]
if search.IsPath(p, g) != false {
t.Error("reverse path")
if search.IsPath(p, g) {
t.Error("IsPath erroneously returns true for a reverse path")
}
p = []graph.Node{p[1], p[0], concrete.GonumNode(2)}
g.AddEdge(concrete.GonumEdge{p[1], p[2]})
if search.IsPath(p, g) != true {
t.Error("three nodes")
p = []graph.Node{p[1], p[0], concrete.Node(2)}
g.AddEdge(concrete.Edge{p[1], p[2]}, 1.0, true)
if !search.IsPath(p, g) {
t.Error("IsPath does not find a correct path for path > 2 nodes")
}
g = concrete.NewGonumGraph(false)
g.AddNode(p[1], []graph.Node{p[0], p[2]})
if search.IsPath(p, g) != true {
t.Error("undirected")
g = concrete.NewGraph()
g.AddEdge(concrete.Edge{p[1], p[0]}, 1.0, false)
g.AddEdge(concrete.Edge{p[1], p[2]}, 1.0, false)
if !search.IsPath(p, g) {
t.Error("IsPath does not correctly account for undirected behavior")
}
}