Skip to content

Commit 4a79528

Browse files
committed
Update tcod.path.dijkstra2d function.
Change the way it handles to be more like other NumPy functions and be more intuitive in general.
1 parent 12d0e11 commit 4a79528

File tree

2 files changed

+54
-16
lines changed

2 files changed

+54
-16
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Added
1313

1414
Changed
1515
- Using `libtcod 1.16.7`.
16+
- `tcod.path.dijkstra2d` now returns the output and accepts an `out` parameter.
17+
18+
Deprecated
19+
- In the future `tcod.path.dijkstra2d` will no longer modify the input by default. Until then an `out` parameter must be given.
1620

1721
Fixed
1822
- Fixed crashes from loading tilesets with non-square tile sizes.

tcod/path.py

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""
1818
import functools
1919
import itertools
20+
import warnings
2021
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
2122

2223
import numpy as np
@@ -26,6 +27,11 @@
2627
from tcod._internal import _check
2728
from 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
3137
def _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

372378
def 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

513547
def _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

523557
def 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

Comments
 (0)