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  }