1717"""
1818import functools
1919import itertools
20+ import warnings
2021from typing import Any , Callable , Dict , List , Optional , Tuple , Union
2122
2223import numpy as np
2627from tcod ._internal import _check
2728from tcod .loader import ffi , lib
2829
30+ try :
31+ from numpy .typing import ArrayLike
32+ except ImportError : # Python < 3.7, Numpy < 1.20
33+ from typing import Any as ArrayLike
34+
2935
3036@ffi .def_extern () # type: ignore
3137def _pycall_path_old (x1 : int , y1 : int , x2 : int , y2 : int , handle : Any ) -> float :
@@ -137,7 +143,7 @@ class NodeCostArray(np.ndarray):
137143 np .uint32 : ("uint32_t*" , _get_pathcost_func ("PathCostArrayUInt32" )),
138144 }
139145
140- def __new__ (cls , array : np . ndarray ) -> "NodeCostArray" :
146+ def __new__ (cls , array : ArrayLike ) -> "NodeCostArray" :
141147 """Validate a numpy array and setup a C callback."""
142148 self = np .asarray (array ).view (cls )
143149 return self
@@ -370,20 +376,20 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]:
370376
371377
372378def dijkstra2d (
373- distance : np . ndarray ,
374- cost : np . ndarray ,
379+ distance : ArrayLike ,
380+ cost : ArrayLike ,
375381 cardinal : Optional [int ] = None ,
376382 diagonal : Optional [int ] = None ,
377383 * ,
378384 edge_map : Any = None ,
379- ) -> None :
385+ out : Optional [np .ndarray ] = ..., # type: ignore
386+ ) -> np .ndarray :
380387 """Return the computed distance of all nodes on a 2D Dijkstra grid.
381388
382- `distance` is an input/output array of node distances. Is this often an
389+ `distance` is an input array of node distances. Is this often an
383390 array filled with maximum finite values and 1 or more points with a low
384391 value such as 0. Distance will flow from these low values to adjacent
385- nodes based the cost to reach those nodes. This array is modified
386- in-place.
392+ nodes based the cost to reach those nodes.
387393
388394 `cost` is an array of node costs. Any node with a cost less than or equal
389395 to 0 is considered blocked off. Positive values are the distance needed to
@@ -398,6 +404,11 @@ def dijkstra2d(
398404 another. This parameter can be hard to understand so you should see how
399405 it's used in the examples.
400406
407+ `out` is the array to fill with the computed Dijkstra distance map.
408+ Having `out` be the same as `distance` will modify the array in-place,
409+ which is normally the fastest option.
410+ If `out` is `None` then the result is returned as a new array.
411+
401412 Example::
402413
403414 >>> import numpy as np
@@ -414,8 +425,7 @@ def dijkstra2d(
414425 array([[ 0, 2147483647, 2147483647],
415426 [2147483647, 2147483647, 2147483647],
416427 [2147483647, 2147483647, 2147483647]]...)
417- >>> tcod.path.dijkstra2d(dist, cost, 2, 3)
418- >>> dist
428+ >>> tcod.path.dijkstra2d(dist, cost, 2, 3, out=dist)
419429 array([[ 0, 2147483647, 10],
420430 [ 2, 2147483647, 8],
421431 [ 4, 5, 7]]...)
@@ -450,8 +460,7 @@ def dijkstra2d(
450460 >>> dist = tcod.path.maxarray((8, 8))
451461 >>> dist[0,0] = 0
452462 >>> cost = np.ones((8, 8), int)
453- >>> tcod.path.dijkstra2d(dist, cost, edge_map=knight_moves)
454- >>> dist
463+ >>> tcod.path.dijkstra2d(dist, cost, edge_map=knight_moves, out=dist)
455464 array([[0, 3, 2, 3, 2, 3, 4, 5],
456465 [3, 4, 1, 2, 3, 4, 3, 4],
457466 [2, 1, 4, 3, 2, 3, 4, 5],
@@ -485,15 +494,39 @@ def dijkstra2d(
485494
486495 .. versionchanged:: 11.13
487496 Added the `edge_map` parameter.
497+
498+ .. versionchanged:: 12.1
499+ Added `out` parameter. Now returns the output array.
488500 """
489- dist = distance
501+ dist = np .asarray (distance )
502+ if out is ...: # type: ignore
503+ out = dist
504+ warnings .warn (
505+ "No `out` parameter was given. "
506+ "Currently this modifies the distance array in-place, but this "
507+ "will change in the future to return a copy instead. "
508+ "To ensure the existing behavior is kept you must add an `out` "
509+ "parameter with the same array as the `distance` parameter." ,
510+ DeprecationWarning ,
511+ stacklevel = 2 ,
512+ )
513+ elif out is None :
514+ out = np .copy (distance )
515+ else :
516+ out [...] = dist
517+
518+ if dist .shape != out .shape :
519+ raise TypeError (
520+ "distance and output must have the same shape %r != %r"
521+ % (dist .shape , out .shape )
522+ )
490523 cost = np .asarray (cost )
491524 if dist .shape != cost .shape :
492525 raise TypeError (
493- "distance and cost must have the same shape %r != %r"
494- % (dist .shape , cost .shape )
526+ "output and cost must have the same shape %r != %r"
527+ % (out .shape , cost .shape )
495528 )
496- c_dist = _export (dist )
529+ c_dist = _export (out )
497530 if edge_map is not None :
498531 if cardinal is not None or diagonal is not None :
499532 raise TypeError (
@@ -508,6 +541,7 @@ def dijkstra2d(
508541 if diagonal is None :
509542 diagonal = 0
510543 _check (lib .dijkstra2d_basic (c_dist , _export (cost ), cardinal , diagonal ))
544+ return out
511545
512546
513547def _compile_bool_edges (edge_map : Any ) -> Tuple [Any , int ]:
@@ -521,7 +555,7 @@ def _compile_bool_edges(edge_map: Any) -> Tuple[Any, int]:
521555
522556
523557def hillclimb2d (
524- distance : np . ndarray ,
558+ distance : ArrayLike ,
525559 start : Tuple [int , int ],
526560 cardinal : Optional [bool ] = None ,
527561 diagonal : Optional [bool ] = None ,
0 commit comments