github.com/gopherd/gonum@v0.0.4/graph/path/internal/testgraphs/limited.go (about) 1 // Copyright ©2015 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package testgraphs 6 7 import ( 8 "errors" 9 "math" 10 11 "github.com/gopherd/gonum/graph" 12 "github.com/gopherd/gonum/graph/iterator" 13 "github.com/gopherd/gonum/graph/simple" 14 ) 15 16 // LimitedVisionGrid is a 2D grid planar undirected graph where the capacity 17 // to determine the presence of edges is dependent on the current and past 18 // positions on the grid. In the absence of information, the grid is 19 // optimistic. 20 type LimitedVisionGrid struct { 21 Grid *Grid 22 23 // Location is the current 24 // location on the grid. 25 Location graph.Node 26 27 // VisionRadius specifies how far 28 // away edges can be detected. 29 VisionRadius float64 30 31 // Known holds a store of known 32 // nodes, if not nil. 33 Known map[int64]bool 34 } 35 36 // MoveTo moves to the node n on the grid and returns a slice of newly seen and 37 // already known edges. MoveTo panics if n is nil. 38 func (l *LimitedVisionGrid) MoveTo(n graph.Node) (new, old []graph.Edge) { 39 l.Location = n 40 row, column := l.RowCol(n.ID()) 41 x := float64(column) 42 y := float64(row) 43 seen := make(map[[2]int64]bool) 44 bound := int(l.VisionRadius + 0.5) 45 for r := row - bound; r <= row+bound; r++ { 46 for c := column - bound; c <= column+bound; c++ { 47 u := l.NodeAt(r, c) 48 if u == nil { 49 continue 50 } 51 uid := u.ID() 52 ux, uy := l.XY(uid) 53 if math.Hypot(x-ux, y-uy) > l.VisionRadius { 54 continue 55 } 56 for _, v := range l.allPossibleFrom(uid) { 57 vid := v.ID() 58 if seen[[2]int64{uid, vid}] { 59 continue 60 } 61 seen[[2]int64{uid, vid}] = true 62 63 vx, vy := l.XY(vid) 64 if !l.Known[vid] && math.Hypot(x-vx, y-vy) > l.VisionRadius { 65 continue 66 } 67 68 e := simple.Edge{F: u, T: v} 69 if !l.Known[uid] || !l.Known[vid] { 70 new = append(new, e) 71 } else { 72 old = append(old, e) 73 } 74 } 75 } 76 } 77 78 if l.Known != nil { 79 for r := row - bound; r <= row+bound; r++ { 80 for c := column - bound; c <= column+bound; c++ { 81 u := l.NodeAt(r, c) 82 if u == nil { 83 continue 84 } 85 uid := u.ID() 86 ux, uy := l.XY(uid) 87 if math.Hypot(x-ux, y-uy) > l.VisionRadius { 88 continue 89 } 90 for _, v := range l.allPossibleFrom(uid) { 91 vid := v.ID() 92 vx, vy := l.XY(vid) 93 if math.Hypot(x-vx, y-vy) > l.VisionRadius { 94 continue 95 } 96 l.Known[vid] = true 97 } 98 l.Known[uid] = true 99 } 100 } 101 102 } 103 104 return new, old 105 } 106 107 // allPossibleFrom returns all the nodes possibly reachable from u. 108 func (l *LimitedVisionGrid) allPossibleFrom(uid int64) []graph.Node { 109 if !l.has(uid) { 110 return nil 111 } 112 nr, nc := l.RowCol(uid) 113 var to []graph.Node 114 for r := nr - 1; r <= nr+1; r++ { 115 for c := nc - 1; c <= nc+1; c++ { 116 v := l.NodeAt(r, c) 117 if v == nil || uid == v.ID() { 118 continue 119 } 120 ur, uc := l.RowCol(uid) 121 vr, vc := l.RowCol(v.ID()) 122 if abs(ur-vr) > 1 || abs(uc-vc) > 1 { 123 continue 124 } 125 if !l.Grid.AllowDiagonal && ur != vr && uc != vc { 126 continue 127 } 128 to = append(to, v) 129 } 130 } 131 return to 132 } 133 134 // RowCol returns the row and column of the id. RowCol will panic if the 135 // node id is outside the range of the grid. 136 func (l *LimitedVisionGrid) RowCol(id int64) (r, c int) { 137 return l.Grid.RowCol(id) 138 } 139 140 // XY returns the cartesian coordinates of n. If n is not a node 141 // in the grid, (NaN, NaN) is returned. 142 func (l *LimitedVisionGrid) XY(id int64) (x, y float64) { 143 if !l.has(id) { 144 return math.NaN(), math.NaN() 145 } 146 r, c := l.RowCol(id) 147 return float64(c), float64(r) 148 } 149 150 // Nodes returns all the nodes in the grid. 151 func (l *LimitedVisionGrid) Nodes() graph.Nodes { 152 nodes := make([]graph.Node, 0, len(l.Grid.open)) 153 for id := range l.Grid.open { 154 nodes = append(nodes, simple.Node(id)) 155 } 156 return iterator.NewOrderedNodes(nodes) 157 } 158 159 // NodeAt returns the node at (r, c). The returned node may be open or closed. 160 func (l *LimitedVisionGrid) NodeAt(r, c int) graph.Node { 161 return l.Grid.NodeAt(r, c) 162 } 163 164 // Node returns the node with the given ID if it exists in the graph, 165 // and nil otherwise. 166 func (l *LimitedVisionGrid) Node(id int64) graph.Node { 167 if l.has(id) { 168 return simple.Node(id) 169 } 170 return nil 171 } 172 173 // has returns whether the node with the given ID is a node in the grid. 174 func (l *LimitedVisionGrid) has(id int64) bool { 175 return 0 <= id && id < int64(len(l.Grid.open)) 176 } 177 178 // From returns nodes that are optimistically reachable from u. 179 func (l *LimitedVisionGrid) From(uid int64) graph.Nodes { 180 if !l.has(uid) { 181 return graph.Empty 182 } 183 184 nr, nc := l.RowCol(uid) 185 var to []graph.Node 186 for r := nr - 1; r <= nr+1; r++ { 187 for c := nc - 1; c <= nc+1; c++ { 188 if v := l.NodeAt(r, c); v != nil && l.HasEdgeBetween(uid, v.ID()) { 189 to = append(to, v) 190 } 191 } 192 } 193 if len(to) == 0 { 194 return graph.Empty 195 } 196 return iterator.NewOrderedNodes(to) 197 } 198 199 // HasEdgeBetween optimistically returns whether an edge is exists between u and v. 200 func (l *LimitedVisionGrid) HasEdgeBetween(uid, vid int64) bool { 201 if uid == vid { 202 return false 203 } 204 ur, uc := l.RowCol(uid) 205 vr, vc := l.RowCol(vid) 206 if abs(ur-vr) > 1 || abs(uc-vc) > 1 { 207 return false 208 } 209 if !l.Grid.AllowDiagonal && ur != vr && uc != vc { 210 return false 211 } 212 213 x, y := l.XY(l.Location.ID()) 214 ux, uy := l.XY(uid) 215 vx, vy := l.XY(vid) 216 uKnown := l.Known[uid] || math.Hypot(x-ux, y-uy) <= l.VisionRadius 217 vKnown := l.Known[vid] || math.Hypot(x-vx, y-vy) <= l.VisionRadius 218 219 switch { 220 case uKnown && vKnown: 221 return l.Grid.HasEdgeBetween(uid, vid) 222 case uKnown: 223 return l.Grid.HasOpen(uid) 224 case vKnown: 225 return l.Grid.HasOpen(vid) 226 default: 227 return true 228 } 229 } 230 231 // Edge optimistically returns the edge from u to v. 232 func (l *LimitedVisionGrid) Edge(uid, vid int64) graph.Edge { 233 return l.WeightedEdgeBetween(uid, vid) 234 } 235 236 // Edge optimistically returns the weighted edge from u to v. 237 func (l *LimitedVisionGrid) WeightedEdge(uid, vid int64) graph.WeightedEdge { 238 return l.WeightedEdgeBetween(uid, vid) 239 } 240 241 // WeightedEdgeBetween optimistically returns the edge between u and v. 242 func (l *LimitedVisionGrid) EdgeBetween(uid, vid int64) graph.Edge { 243 return l.WeightedEdgeBetween(uid, vid) 244 } 245 246 // WeightedEdgeBetween optimistically returns the weighted edge between u and v. 247 func (l *LimitedVisionGrid) WeightedEdgeBetween(uid, vid int64) graph.WeightedEdge { 248 if l.HasEdgeBetween(uid, vid) { 249 if !l.Grid.AllowDiagonal || l.Grid.UnitEdgeWeight { 250 return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: 1} 251 } 252 ux, uy := l.XY(uid) 253 vx, vy := l.XY(vid) 254 return simple.WeightedEdge{F: simple.Node(uid), T: simple.Node(vid), W: math.Hypot(ux-vx, uy-vy)} 255 } 256 return nil 257 } 258 259 // Weight returns the weight of the given edge. 260 func (l *LimitedVisionGrid) Weight(xid, yid int64) (w float64, ok bool) { 261 if xid == yid { 262 return 0, true 263 } 264 if !l.HasEdgeBetween(xid, yid) { 265 return math.Inf(1), false 266 } 267 if e := l.EdgeBetween(xid, yid); e != nil { 268 if !l.Grid.AllowDiagonal || l.Grid.UnitEdgeWeight { 269 return 1, true 270 } 271 ux, uy := l.XY(e.From().ID()) 272 vx, vy := l.XY(e.To().ID()) 273 return math.Hypot(ux-vx, uy-vy), true 274 275 } 276 return math.Inf(1), true 277 } 278 279 // String returns a string representation of the grid. 280 func (l *LimitedVisionGrid) String() string { 281 b, _ := l.Render(nil) 282 return string(b) 283 } 284 285 // Render returns a text representation of the graph 286 // with the given path included. If the path is not a path 287 // in the grid Render returns a non-nil error and the 288 // path up to that point. 289 func (l *LimitedVisionGrid) Render(path []graph.Node) ([]byte, error) { 290 rows, cols := l.Grid.Dims() 291 b := make([]byte, rows*(cols+1)-1) 292 for r := 0; r < rows; r++ { 293 for c := 0; c < cols; c++ { 294 if !l.Known[int64(r*cols+c)] { 295 b[r*(cols+1)+c] = Unknown 296 } else if l.Grid.open[r*cols+c] { 297 b[r*(cols+1)+c] = Open 298 } else { 299 b[r*(cols+1)+c] = Closed 300 } 301 } 302 if r < rows-1 { 303 b[r*(cols+1)+cols] = '\n' 304 } 305 } 306 307 // We don't use topo.IsPathIn at the outset because we 308 // want to draw as much as possible before failing. 309 for i, n := range path { 310 id := n.ID() 311 if !l.has(id) || (i != 0 && !l.HasEdgeBetween(path[i-1].ID(), id)) { 312 if 0 <= id && id < int64(len(l.Grid.open)) { 313 r, c := l.RowCol(id) 314 b[r*(cols+1)+c] = '!' 315 } 316 return b, errors.New("grid: not a path in graph") 317 } 318 r, c := l.RowCol(id) 319 switch i { 320 case len(path) - 1: 321 b[r*(cols+1)+c] = 'G' 322 case 0: 323 b[r*(cols+1)+c] = 'S' 324 default: 325 b[r*(cols+1)+c] = 'o' 326 } 327 } 328 return b, nil 329 }