diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 320cf038c..424ae5977 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -75,7 +75,7 @@ repos: - id: mypy files: src args: [] - additional_dependencies: [numpy==1.20.*, uhi, types-dataclasses] + additional_dependencies: [numpy==1.21.*, uhi, types-dataclasses] - repo: https://github.com/mgedmin/check-manifest rev: "0.46" diff --git a/src/boost_histogram/_core/axis/__init__.pyi b/src/boost_histogram/_core/axis/__init__.pyi index 5947ee9b3..63f24a4fe 100644 --- a/src/boost_histogram/_core/axis/__init__.pyi +++ b/src/boost_histogram/_core/axis/__init__.pyi @@ -34,13 +34,13 @@ class _BaseAxis: @property def extent(self) -> int: ... @property - def edges(self) -> np.ndarray: ... + def edges(self) -> "np.typing.NDArray[Any]": ... @property - def centers(self) -> np.ndarray: ... + def centers(self) -> "np.typing.NDArray[Any]": ... @property - def widths(self) -> np.ndarray: ... - def index(self, arg0: ArrayLike) -> int | np.ndarray: ... - def value(self, arg0: ArrayLike) -> float | np.ndarray: ... + def widths(self) -> "np.typing.NDArray[Any]": ... + def index(self, arg0: ArrayLike) -> int | "np.typing.NDArray[Any]": ... + def value(self, arg0: ArrayLike) -> float | "np.typing.NDArray[Any]": ... class _BaseRegular(_BaseAxis): def __init__(self, bins: int, start: float, stop: float) -> None: ... diff --git a/src/boost_histogram/_core/hist.pyi b/src/boost_histogram/_core/hist.pyi index 8fc5d48eb..523f37419 100644 --- a/src/boost_histogram/_core/hist.pyi +++ b/src/boost_histogram/_core/hist.pyi @@ -22,8 +22,8 @@ class _BaseHistogram: def __copy__(self: T) -> T: ... def __deepcopy__(self: T, memo: Any) -> T: ... def __iadd__(self: T, other: _BaseHistogram) -> T: ... - def to_numpy(self, flow: bool = ...) -> Tuple[np.ndarray, ...]: ... - def view(self, flow: bool = ...) -> np.ndarray: ... + def to_numpy(self, flow: bool = ...) -> Tuple["np.typing.NDArray[Any]", ...]: ... + def view(self, flow: bool = ...) -> "np.typing.NDArray[Any]": ... def axis(self, i: int = ...) -> axis._BaseAxis: ... def fill(self, *args: ArrayLike, weight: ArrayLike | None = ...) -> None: ... def empty(self, flow: bool = ...) -> bool: ... diff --git a/src/boost_histogram/_internal/axestuple.py b/src/boost_histogram/_internal/axestuple.py index 549cdb397..d37e5f981 100644 --- a/src/boost_histogram/_internal/axestuple.py +++ b/src/boost_histogram/_internal/axestuple.py @@ -17,12 +17,12 @@ class ArrayTuple(tuple): # type: ignore def __getattr__(self, name: str) -> Any: if name in self._REDUCTIONS: - return partial(getattr(np, name), np.broadcast_arrays(*self)) + return partial(getattr(np, name), np.broadcast_arrays(*self)) # type: ignore else: return self.__class__(getattr(a, name) for a in self) def __dir__(self) -> List[str]: - names = dir(self.__class__) + dir(np.ndarray) + names = dir(self.__class__) + dir("np.typing.NDArray[Any]") return sorted(n for n in names if not n.startswith("_")) def __call__(self, *args: Any, **kwargs: Any) -> Any: @@ -34,7 +34,7 @@ def broadcast(self: A) -> A: Use this method to broadcast them out into their full memory representation. """ - return self.__class__(np.broadcast_arrays(*self)) + return self.__class__(np.broadcast_arrays(*self)) # type: ignore B = TypeVar("B", bound="AxesTuple") @@ -56,17 +56,17 @@ def extent(self) -> Tuple[int, ...]: @property def centers(self) -> ArrayTuple: gen = (s.centers for s in self) - return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) + return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) # type: ignore @property def edges(self) -> ArrayTuple: gen = (s.edges for s in self) - return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) + return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) # type: ignore @property def widths(self) -> ArrayTuple: gen = (s.widths for s in self) - return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) + return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) # type: ignore def value(self, *indexes: float) -> Tuple[float, ...]: if len(indexes) != len(self): diff --git a/src/boost_histogram/_internal/axis.py b/src/boost_histogram/_internal/axis.py index 1ff830b13..ca467ee5e 100644 --- a/src/boost_histogram/_internal/axis.py +++ b/src/boost_histogram/_internal/axis.py @@ -248,18 +248,18 @@ def __getitem__(self, i: AxCallOrInt) -> Union[int, str, Tuple[float, float]]: return self.bin(i) @property - def edges(self) -> np.ndarray: + def edges(self) -> "np.typing.NDArray[Any]": return self._ax.edges # type: ignore @property - def centers(self) -> np.ndarray: + def centers(self) -> "np.typing.NDArray[Any]": """ An array of bin centers. """ return self._ax.centers # type: ignore @property - def widths(self) -> np.ndarray: + def widths(self) -> "np.typing.NDArray[Any]": """ An array of bin widths. """ diff --git a/src/boost_histogram/_internal/hist.py b/src/boost_histogram/_internal/hist.py index 9419458c9..0cda0b9a8 100644 --- a/src/boost_histogram/_internal/hist.py +++ b/src/boost_histogram/_internal/hist.py @@ -65,7 +65,9 @@ T = TypeVar("T") -def _fill_cast(value: T, *, inner: bool = False) -> Union[T, np.ndarray, Tuple[T, ...]]: +def _fill_cast( + value: T, *, inner: bool = False +) -> Union[T, "np.typing.NDArray[Any]", Tuple[T, ...]]: """ Convert to NumPy arrays. Some buffer objects do not get converted by forcecast. If not called by itself (inner=False), then will work through one level of tuple/list. @@ -297,13 +299,13 @@ def ndim(self) -> int: def view( self, flow: bool = False - ) -> Union[np.ndarray, WeightedSumView, WeightedMeanView, MeanView]: + ) -> Union["np.typing.NDArray[Any]", WeightedSumView, WeightedMeanView, MeanView]: """ Return a view into the data, optionally with overflow turned on. """ return _to_view(self._hist.view(flow)) - def __array__(self) -> np.ndarray: + def __array__(self) -> "np.typing.NDArray[Any]": return self.view(False) def __eq__(self, other: Any) -> bool: @@ -312,11 +314,15 @@ def __eq__(self, other: Any) -> bool: def __ne__(self, other: Any) -> bool: return (not hasattr(other, "_hist")) or self._hist != other._hist - def __add__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __add__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: result = self.copy(deep=False) return result.__iadd__(other) - def __iadd__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __iadd__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: if isinstance(other, (int, float)) and other == 0: return self self._compute_inplace_op("__iadd__", other) @@ -326,36 +332,52 @@ def __iadd__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: return self - def __radd__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __radd__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: return self + other # If these fail, the underlying object throws the correct error - def __mul__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __mul__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: result = self.copy(deep=False) return result._compute_inplace_op("__imul__", other) - def __rmul__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __rmul__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: return self * other - def __truediv__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __truediv__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: result = self.copy(deep=False) return result._compute_inplace_op("__itruediv__", other) - def __div__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __div__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: result = self.copy(deep=False) return result._compute_inplace_op("__idiv__", other) - def __idiv__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __idiv__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: return self._compute_inplace_op("__idiv__", other) - def __itruediv__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __itruediv__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: return self._compute_inplace_op("__itruediv__", other) - def __imul__(self: H, other: Union["Histogram", np.ndarray, float]) -> H: + def __imul__( + self: H, other: Union["Histogram", "np.typing.NDArray[Any]", float] + ) -> H: return self._compute_inplace_op("__imul__", other) def _compute_inplace_op( - self: H, name: str, other: Union["Histogram", np.ndarray, float] + self: H, name: str, other: Union["Histogram", "np.typing.NDArray[Any]", float] ) -> H: # Also takes CppHistogram, but that confuses mypy because it's hard to pick out if isinstance(other, Histogram): @@ -442,26 +464,26 @@ def fill( }: raise RuntimeError("Mean histograms do not support threaded filling") - data = [np.array_split(a, threads) for a in args_ars] + data = [np.array_split(a, threads) for a in args_ars] # type: ignore if weight is None or np.isscalar(weight): assert threads is not None weights = [weight_ars] * threads else: - weights = np.array_split(weight_ars, threads) + weights = np.array_split(weight_ars, threads) # type: ignore if sample_ars is None or np.isscalar(sample_ars): assert threads is not None samples = [sample_ars] * threads else: - samples = np.array_split(sample_ars, threads) + samples = np.array_split(sample_ars, threads) # type: ignore if self._hist._storage_type is _core.storage.atomic_int64: def fun( weight: Optional[ArrayLike], sample: Optional[ArrayLike], - *args: np.ndarray, + *args: "np.typing.NDArray[Any]", ) -> None: self._hist.fill(*args, weight=weight, sample=sample) @@ -471,7 +493,7 @@ def fun( def fun( weight: Optional[ArrayLike], sample: Optional[ArrayLike], - *args: np.ndarray, + *args: "np.typing.NDArray[Any]", ) -> None: local_hist = self._hist.__copy__() local_hist.reset() @@ -646,7 +668,10 @@ def _compute_commonindex( def to_numpy( self, flow: bool = False, *, dd: bool = False, view: bool = False - ) -> Union[Tuple[np.ndarray, ...], Tuple[np.ndarray, Tuple[np.ndarray, ...]]]: + ) -> Union[ + Tuple["np.typing.NDArray[Any]", ...], + Tuple["np.typing.NDArray[Any]", Tuple["np.typing.NDArray[Any]", ...]], + ]: """ Convert to a NumPy style tuple of return arrays. Edges are converted to match NumPy standards, with upper edge inclusive, unlike @@ -887,7 +912,7 @@ def __setitem__( if ( value.ndim > 0 and len(view.dtype) > 0 # type: ignore - and len(value.dtype) == 0 # type: ignore + and len(value.dtype) == 0 and len(view.dtype) == value.shape[-1] # type: ignore ): value_shape = value.shape[:-1] @@ -984,7 +1009,7 @@ def kind(self) -> Kind: else: return Kind.COUNT - def values(self, flow: bool = False) -> np.ndarray: + def values(self, flow: bool = False) -> "np.typing.NDArray[Any]": """ Returns the accumulated values. The counts for simple histograms, the sum of weights for weighted histograms, the mean for profiles, etc. @@ -995,7 +1020,7 @@ def values(self, flow: bool = False) -> np.ndarray: :param flow: Enable flow bins. Not part of PlottableHistogram, but included for consistency with other methods and flexibility. - :return: np.ndarray[np.float64] + :return: "np.typing.NDArray[Any]"[np.float64] """ view = self.view(flow) @@ -1005,7 +1030,7 @@ def values(self, flow: bool = False) -> np.ndarray: else: return view.value # type: ignore - def variances(self, flow: bool = False) -> Optional[np.ndarray]: + def variances(self, flow: bool = False) -> Optional["np.typing.NDArray[Any]"]: """ Returns the estimated variance of the accumulated values. The sum of squared weights for weighted histograms, the variance of samples for profiles, etc. @@ -1026,7 +1051,7 @@ def variances(self, flow: bool = False) -> Optional[np.ndarray]: :param flow: Enable flow bins. Not part of PlottableHistogram, but included for consistency with other methods and flexibility. - :return: np.ndarray[np.float64] + :return: "np.typing.NDArray[Any]"[np.float64] """ view = self.view(flow) @@ -1053,7 +1078,7 @@ def variances(self, flow: bool = False) -> Optional[np.ndarray]: else: return view.variance # type: ignore - def counts(self, flow: bool = False) -> np.ndarray: + def counts(self, flow: bool = False) -> "np.typing.NDArray[Any]": """ Returns the number of entries in each bin for an unweighted histogram or profile and an effective number of entries (defined below) @@ -1073,7 +1098,7 @@ def counts(self, flow: bool = False) -> np.ndarray: The larger the spread in weights, the smaller it is, but it is always 0 if filled 0 times, and 1 if filled once, and more than 1 otherwise. - :return: np.ndarray[np.float64] + :return: "np.typing.NDArray[Any]"[np.float64] """ view = self.view(flow) diff --git a/src/boost_histogram/_internal/view.py b/src/boost_histogram/_internal/view.py index ae81cc198..788704bf5 100644 --- a/src/boost_histogram/_internal/view.py +++ b/src/boost_histogram/_internal/view.py @@ -6,11 +6,11 @@ from .typing import ArrayLike, StrIndex, Ufunc -class View(np.ndarray): +class View(np.ndarray): # type: ignore __slots__ = () _FIELDS: ClassVar[Tuple[str, ...]] - def __getitem__(self, ind: StrIndex) -> np.ndarray: + def __getitem__(self, ind: StrIndex) -> "np.typing.NDArray[Any]": sliced = super().__getitem__(ind) # If the shape is empty, return the parent type @@ -97,13 +97,13 @@ class WeightedSumView(View): __slots__ = () _PARENT = WeightedSum - value: np.ndarray - variance: np.ndarray + value: "np.typing.NDArray[Any]" + variance: "np.typing.NDArray[Any]" # Could be implemented on master View def __array_ufunc__( self, ufunc: Ufunc, method: str, *inputs: Any, **kwargs: Any - ) -> np.ndarray: + ) -> "np.typing.NDArray[Any]": # This one is defined for record arrays, so just use it # (Doesn't get picked up the pass-through) @@ -205,13 +205,13 @@ class WeightedMeanView(View): __slots__ = () _PARENT = WeightedMean - sum_of_weights: np.ndarray - sum_of_weights_squared: np.ndarray - value: np.ndarray - _sum_of_weighted_deltas_squared: np.ndarray + sum_of_weights: "np.typing.NDArray[Any]" + sum_of_weights_squared: "np.typing.NDArray[Any]" + value: "np.typing.NDArray[Any]" + _sum_of_weighted_deltas_squared: "np.typing.NDArray[Any]" @property - def variance(self) -> np.ndarray: + def variance(self) -> "np.typing.NDArray[Any]": with np.errstate(divide="ignore", invalid="ignore"): return self["_sum_of_weighted_deltas_squared"] / ( # type: ignore self["sum_of_weights"] @@ -224,20 +224,20 @@ class MeanView(View): __slots__ = () _PARENT = Mean - count: np.ndarray - value: np.ndarray - _sum_of_deltas_squared: np.ndarray + count: "np.typing.NDArray[Any]" + value: "np.typing.NDArray[Any]" + _sum_of_deltas_squared: "np.typing.NDArray[Any]" # Variance is a computation @property - def variance(self) -> np.ndarray: + def variance(self) -> "np.typing.NDArray[Any]": with np.errstate(divide="ignore", invalid="ignore"): - return self["_sum_of_deltas_squared"] / (self["count"] - 1) # type: ignore + return self["_sum_of_deltas_squared"] / (self["count"] - 1) def _to_view( - item: np.ndarray, value: bool = False -) -> Union[np.ndarray, WeightedSumView, WeightedMeanView, MeanView]: + item: "np.typing.NDArray[Any]", value: bool = False +) -> Union["np.typing.NDArray[Any]", WeightedSumView, WeightedMeanView, MeanView]: for cls in View.__subclasses__(): if cls._FIELDS == item.dtype.names: ret = item.view(cls) diff --git a/src/boost_histogram/numpy.py b/src/boost_histogram/numpy.py index 112478103..a0eb8666a 100644 --- a/src/boost_histogram/numpy.py +++ b/src/boost_histogram/numpy.py @@ -67,7 +67,7 @@ def histogramdd( if np.issubdtype(type(b), np.integer): if r is None: # Nextafter may affect bin edges slightly - r = (np.min(a[n]), np.max(a[n])) + r = (np.amin(a[n]), np.amax(a[n])) cpp_ax = _core.axis.regular_numpy(b, r[0], r[1]) new_ax = _cast(None, cpp_ax, _axis.Axis) axs.append(new_ax) @@ -183,6 +183,6 @@ def histogram( lets you set the number of threads to fill with (0 for auto, None for 1). """ - f.__doc__ = H.format(n.__name__) + n.__doc__ + f.__doc__ = H.format(n.__name__) + (n.__doc__ or "") del f, n, H