gonum.org/v1/gonum@v0.14.0/graph/path/internal/testgraphs/grid.go (about) 1 // Copyright ©2014 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package testgraphs 6 7 import ( 8 "errors" 9 "fmt" 10 "math" 11 12 "gonum.org/v1/gonum/graph" 13 "gonum.org/v1/gonum/graph/iterator" 14 "gonum.org/v1/gonum/graph/simple" 15 ) 16 17 const ( 18 Closed = '*' // Closed is the closed grid node representation. 19 Open = '.' // Open is the open grid node representation. 20 Unknown = '?' // Unknown is the unknown grid node representation. 21 ) 22 23 // Grid is a 2D grid planar undirected graph. 24 type Grid struct { 25 // AllowDiagonal specifies whether 26 // diagonally adjacent nodes can 27 // be connected by an edge. 28 AllowDiagonal bool 29 // UnitEdgeWeight specifies whether 30 // finite edge weights are returned as 31 // the unit length. Otherwise edge 32 // weights are the Euclidean distance 33 // between connected nodes. 34 UnitEdgeWeight bool 35 36 // AllVisible specifies whether 37 // non-open nodes are visible 38 // in calls to Nodes and HasNode. 39 AllVisible bool 40 41 open []bool 42 r, c int 43 } 44 45 // NewGrid returns an r by c grid with all positions 46 // set to the specified open state. 47 func NewGrid(r, c int, open bool) *Grid { 48 states := make([]bool, r*c) 49 if open { 50 for i := range states { 51 states[i] = true 52 } 53 } 54 return &Grid{ 55 open: states, 56 r: r, 57 c: c, 58 } 59 } 60 61 // NewGridFrom returns a grid specified by the rows strings. All rows must 62 // be the same length and must only contain the Open or Closed characters, 63 // NewGridFrom will panic otherwise. 64 func NewGridFrom(rows ...string) *Grid { 65 if len(rows) == 0 { 66 return nil 67 } 68 for i, r := range rows[:len(rows)-1] { 69 if len(r) != len(rows[i+1]) { 70 panic("grid: unequal row lengths") 71 } 72 } 73 states := make([]bool, 0, len(rows)*len(rows[0])) 74 for _, r := range rows { 75 for _, b := range r { 76 switch b { 77 case Closed: 78 states = append(states, false) 79 case Open: 80 states = append(states, true) 81 default: 82 panic(fmt.Sprintf("grid: invalid state: %q", r)) 83 } 84 } 85 } 86 return &Grid{ 87 open: states, 88 r: len(rows), 89 c: len(rows[0]), 90 } 91 } 92 93 // Nodes returns all the open nodes in the grid if AllVisible is 94 // false, otherwise all nodes are returned. 95 func (g *Grid) Nodes() graph.Nodes { 96 var nodes []graph.Node 97 for id, ok := range g.open { 98 if ok || g.AllVisible { 99 nodes = append(nodes, simple.Node(id)) 100 } 101 } 102 return iterator.NewOrderedNodes(nodes) 103 } 104 105 // Node returns the node with the given ID if it exists in the graph, 106 // and nil otherwise. 107 func (g *Grid) Node(id int64) graph.Node { 108 if g.has(id) { 109 return simple.Node(id) 110 } 111 return nil 112 } 113 114 // has returns whether id represents a node in the grid. The state of 115 // the AllVisible field determines whether a non-open node is present. 116 func (g *Grid) has(id int64) bool { 117 return 0 <= id && id < int64(len(g.open)) && (g.AllVisible || g.open[id]) 118 } 119 120 // HasOpen returns whether n is an open node in the grid. 121 func (g *Grid) HasOpen(id int64) bool { 122 return 0 <= id && id < int64(len(g.open)) && g.open[id] 123 } 124 125 // Set sets the node at position (r, c) to the specified open state. 126 func (g *Grid) Set(r, c int, open bool) { 127 if r < 0 || r >= g.r { 128 panic("grid: illegal row index") 129 } 130 if c < 0 || c >= g.c { 131 panic("grid: illegal column index") 132 } 133 g.open[r*g.c+c] = open 134 } 135 136 // Dims returns the dimensions of the grid. 137 func (g *Grid) Dims() (r, c int) { 138 return g.r, g.c 139 } 140 141 // RowCol returns the row and column of the id. RowCol will panic if the 142 // node id is outside the range of the grid. 143 func (g *Grid) RowCol(id int64) (r, c int) { 144 if id < 0 || int64(len(g.open)) <= id { 145 panic("grid: illegal node id") 146 } 147 return int(id) / g.c, int(id) % g.c 148 } 149 150 // XY returns the cartesian coordinates of n. If n is not a node 151 // in the grid, (NaN, NaN) is returned. 152 func (g *Grid) XY(id int64) (x, y float64) { 153 if !g.has(id) { 154 return math.NaN(), math.NaN() 155 } 156 r, c := g.RowCol(id) 157 return float64(c), float64(r) 158 } 159 160 // NodeAt returns the node at (r, c). The returned node may be open or closed. 161 func (g *Grid) NodeAt(r, c int) graph.Node { 162 if r < 0 || r >= g.r || c < 0 || c >= g.c { 163 return nil 164 } 165 return simple.Node(r*g.c + c) 166 } 167 168 // From returns all the nodes reachable from u. Reachabilty requires that both 169 // ends of an edge must be open. 170 func (g *Grid) From(uid int64) graph.Nodes { 171 if !g.HasOpen(uid) { 172 return graph.Empty 173 } 174 nr, nc := g.RowCol(uid) 175 var to []graph.Node 176 for r := nr - 1; r <= nr+1; r++ { 177 for c := nc - 1; c <= nc+1; c++ { 178 if v := g.NodeAt(r, c); v != nil && g.HasEdgeBetween(uid, v.ID()) { 179 to = append(to, v) 180 } 181 } 182 } 183 if len(to) == 0 { 184 return graph.Empty 185 } 186 return iterator.NewOrderedNodes(to) 187 } 188 189 // HasEdgeBetween returns whether there is an edge between u and v. 190 func (g *Grid) HasEdgeBetween(uid, vid int64) bool { 191 if !g.HasOpen(uid) || !g.HasOpen(vid) || uid == vid { 192 return false 193 } 194 ur, uc := g.RowCol(uid) 195 vr, vc := g.RowCol(vid) 196 if abs(ur-vr) > 1 || abs(uc-vc) > 1 { 197 return false 198 } 199 return g.AllowDiagonal || ur == vr || uc == vc 200 } 201 202 func abs(i int) int { 203 if i < 0 { 204 return -i 205 } 206 return i 207 } 208 209 // Edge returns the edge between u and v. 210 func (g *Grid) Edge(uid, vid int64) graph.Edge { 211 return g.WeightedEdgeBetween(uid, vid) 212 } 213 214 // WeightedEdge returns the weighted edge between u and v. 215 func (g *Grid) WeightedEdge(uid, vid int64) graph.WeightedEdge { 216 return g.WeightedEdgeBetween(uid, vid) 217 } 218 219 // EdgeBetween returns the edge between u and v. 220 func (g *Grid) EdgeBetween(uid, vid int64) graph.Edge { 221 return g.WeightedEdgeBetween(uid, vid) 222 } 223 224 // WeightedEdgeBetween returns the weighted edge between u and v. 225 func (g *Grid) WeightedEdgeBetween(uid, vid int64) graph.WeightedEdge { 226 if g.HasEdgeBetween(uid, vid) { 227 if !g.AllowDiagonal || g.UnitEdgeWeight { 228 return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: 1} 229 } 230 ux, uy := g.XY(uid) 231 vx, vy := g.XY(vid) 232 return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: math.Hypot(ux-vx, uy-vy)} 233 } 234 return nil 235 } 236 237 // Weight returns the weight of the given edge. 238 func (g *Grid) Weight(xid, yid int64) (w float64, ok bool) { 239 if xid == yid { 240 return 0, true 241 } 242 if !g.HasEdgeBetween(xid, yid) { 243 return math.Inf(1), false 244 } 245 if e := g.EdgeBetween(xid, yid); e != nil { 246 if !g.AllowDiagonal || g.UnitEdgeWeight { 247 return 1, true 248 } 249 ux, uy := g.XY(e.From().ID()) 250 vx, vy := g.XY(e.To().ID()) 251 return math.Hypot(ux-vx, uy-vy), true 252 } 253 return math.Inf(1), true 254 } 255 256 // String returns a string representation of the grid. 257 func (g *Grid) String() string { 258 b, _ := g.Render(nil) 259 return string(b) 260 } 261 262 // Render returns a text representation of the graph 263 // with the given path included. If the path is not a path 264 // in the grid Render returns a non-nil error and the 265 // path up to that point. 266 func (g *Grid) Render(path []graph.Node) ([]byte, error) { 267 b := make([]byte, g.r*(g.c+1)-1) 268 for r := 0; r < g.r; r++ { 269 for c := 0; c < g.c; c++ { 270 if g.open[r*g.c+c] { 271 b[r*(g.c+1)+c] = Open 272 } else { 273 b[r*(g.c+1)+c] = Closed 274 } 275 } 276 if r < g.r-1 { 277 b[r*(g.c+1)+g.c] = '\n' 278 } 279 } 280 281 // We don't use topo.IsPathIn at the outset because we 282 // want to draw as much as possible before failing. 283 for i, n := range path { 284 id := n.ID() 285 if !g.has(id) || (i != 0 && !g.HasEdgeBetween(path[i-1].ID(), id)) { 286 if 0 <= id && id < int64(len(g.open)) { 287 r, c := g.RowCol(id) 288 b[r*(g.c+1)+c] = '!' 289 } 290 return b, errors.New("grid: not a path in graph") 291 } 292 r, c := g.RowCol(id) 293 switch i { 294 case len(path) - 1: 295 b[r*(g.c+1)+c] = 'G' 296 case 0: 297 b[r*(g.c+1)+c] = 'S' 298 default: 299 b[r*(g.c+1)+c] = 'o' 300 } 301 } 302 return b, nil 303 }