Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 36 additions & 37 deletions src/boost_histogram/histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

import collections.abc
import copy
import enum
import logging
import sys
import threading
import typing
import warnings
from collections.abc import Callable, Iterable, Mapping
from collections.abc import Iterable, Mapping
from enum import Enum
from os import cpu_count
from types import EllipsisType
from typing import (
TYPE_CHECKING,
Any,
Callable,
ClassVar,
NewType,
SupportsIndex,
TypeAlias,
TypeVar,
Union,
)

import numpy as np
Expand All @@ -35,7 +35,7 @@
from .view import MeanView, WeightedMeanView, WeightedSumView, _to_view

if TYPE_CHECKING:
pass
from builtins import ellipsis


try:
Expand All @@ -55,10 +55,13 @@
raise new_exception from err


class Kind(enum.StrEnum):
# This is a StrEnum as defined in Python 3.11
class Kind(str, Enum):
COUNT = "COUNT"
MEAN = "MEAN"

__str__ = str.__str__


__all__ = [
"Histogram",
Expand Down Expand Up @@ -99,13 +102,11 @@ def __dir__() -> list[str]:

CppAxis = NewType("CppAxis", object)

SimpleIndexing: TypeAlias = SupportsIndex | slice | RebinProtocol
InnerIndexing: TypeAlias = SimpleIndexing | Callable[[Axis], int]
FullInnerIndexing: TypeAlias = InnerIndexing | list[InnerIndexing]
IndexingWithMapping: TypeAlias = FullInnerIndexing | Mapping[int, FullInnerIndexing]
IndexingExpr: TypeAlias = (
IndexingWithMapping | tuple[IndexingWithMapping, ...] | EllipsisType
)
SimpleIndexing = Union[SupportsIndex, slice, RebinProtocol]
InnerIndexing = Union[SimpleIndexing, Callable[[Axis], int]]
FullInnerIndexing = Union[InnerIndexing, list[InnerIndexing]]
IndexingWithMapping = Union[FullInnerIndexing, Mapping[int, FullInnerIndexing]]
IndexingExpr = Union[IndexingWithMapping, tuple[IndexingWithMapping, ...], "ellipsis"]

T = TypeVar("T")

Expand All @@ -121,7 +122,7 @@ def _fill_cast(
return value

if not inner and isinstance(value, (tuple, list)):
return tuple(_fill_cast(a, inner=True) for a in value) # type: ignore[misc]
return tuple(_fill_cast(a, inner=True) for a in value)

if hasattr(value, "__iter__") or hasattr(value, "__array__"):
return np.asarray(value)
Expand Down Expand Up @@ -184,9 +185,9 @@ def _combine_group_contents(
pos = [slice(None)] * (i)
if new_view.dtype.names:
for field in new_view.dtype.names:
new_view[(*pos, jj, ...)][field] += reduced_view[(*pos, j, ...)][field] # type: ignore[arg-type]
new_view[(*pos, jj, ...)][field] += reduced_view[(*pos, j, ...)][field]
else:
new_view[(*pos, jj, ...)] += reduced_view[(*pos, j, ...)] # type: ignore[arg-type]
new_view[(*pos, jj, ...)] += reduced_view[(*pos, j, ...)]


H = TypeVar("H", bound="Histogram")
Expand Down Expand Up @@ -496,7 +497,7 @@ def __array__(
kwargs = {}
if copy is not None:
kwargs["copy"] = copy
return np.asarray(self.view(False), dtype=dtype, **kwargs) # type: ignore[call-overload, no-any-return]
return np.asarray(self.view(False), dtype=dtype, **kwargs) # type: ignore[call-overload]

__hash__ = None # type: ignore[assignment]

Expand Down Expand Up @@ -576,12 +577,10 @@ def _compute_inplace_op(
msg = f"Number of dimensions {len(other.shape)} must match histogram {self.ndim}"
raise ValueError(msg)

if all(a in {b, 1} for a, b in zip(other.shape, self.shape, strict=False)):
if all(a in {b, 1} for a, b in zip(other.shape, self.shape)):
view = self.view(flow=False)
getattr(view, name)(other)
elif all(
a in {b, 1} for a, b in zip(other.shape, self.axes.extent, strict=False)
):
elif all(a in {b, 1} for a, b in zip(other.shape, self.axes.extent)):
view = self.view(flow=True)
getattr(view, name)(other)
else:
Expand Down Expand Up @@ -642,7 +641,7 @@ def fill(
threads = cpu_count()

if threads is None or threads == 1:
self._hist.fill(*args_ars, weight=weight_ars, sample=sample_ars) # type: ignore[arg-type]
self._hist.fill(*args_ars, weight=weight_ars, sample=sample_ars)
return self

if self._hist._storage_type in {
Expand All @@ -652,7 +651,7 @@ def fill(
raise RuntimeError("Mean histograms do not support threaded filling")

data: list[list[np.typing.NDArray[Any]] | list[str]] = [
np.array_split(a, threads) if not isinstance(a, str) else [a] * threads # type: ignore[arg-type, list-item]
np.array_split(a, threads) if not isinstance(a, str) else [a] * threads
for a in args_ars
]

Expand All @@ -661,14 +660,14 @@ def fill(
assert threads is not None
weights = [weight_ars] * threads
else:
weights = np.array_split(weight_ars, threads) # type: ignore[arg-type]
weights = np.array_split(weight_ars, threads)

samples: list[Any]
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) # type: ignore[arg-type]
samples = np.array_split(sample_ars, threads)

if self._hist._storage_type is _core.storage.atomic_int64:

Expand All @@ -695,7 +694,7 @@ def fun(

thread_list = [
threading.Thread(target=fun, args=arrays)
for arrays in zip(weights, samples, *data, strict=False)
for arrays in zip(weights, samples, *data)
]

for thread in thread_list:
Expand Down Expand Up @@ -903,7 +902,7 @@ def to_numpy(
hist, *edges = self._hist.to_numpy(flow)
hist = self.view(flow=flow) if view else self.values(flow=flow)

return (hist, edges) if dd else (hist, *edges) # type: ignore[return-value]
return (hist, edges) if dd else (hist, *edges)

def copy(self, *, deep: bool = True) -> Self:
"""
Expand Down Expand Up @@ -1186,7 +1185,7 @@ def __setitem__(self, index: IndexingExpr, value: ArrayLike | Accumulator) -> No
if (
in_array.ndim > 0
and len(view.dtype) > 0
and len(in_array.dtype) == 0 # type: ignore[arg-type]
and len(in_array.dtype) == 0
and len(view.dtype) == in_array.shape[-1]
):
value_shape = in_array.shape[:-1]
Expand Down Expand Up @@ -1319,8 +1318,8 @@ def values(self, flow: bool = False) -> np.typing.NDArray[Any]:
view: Any = self.view(flow)
# TODO: Might be a NumPy typing bug
if len(view.dtype) == 0:
return view # type: ignore[no-any-return]
return view.value # type: ignore[no-any-return]
return view
return view.value

def variances(self, flow: bool = False) -> np.typing.NDArray[Any] | None:
"""
Expand Down Expand Up @@ -1352,22 +1351,22 @@ def variances(self, flow: bool = False) -> np.typing.NDArray[Any] | None:

if hasattr(view, "sum_of_weights"):
valid = view.sum_of_weights**2 > view.sum_of_weights_squared
return np.divide( # type: ignore[no-any-return]
return np.divide(
view.variance,
view.sum_of_weights,
out=np.full(view.sum_of_weights.shape, np.nan),
where=valid,
)

if hasattr(view, "count"):
return np.divide( # type: ignore[no-any-return]
return np.divide(
view.variance,
view.count,
out=np.full(view.count.shape, np.nan),
where=view.count > 1,
)

return view.variance # type: ignore[no-any-return]
return view.variance

def counts(self, flow: bool = False) -> np.typing.NDArray[Any]:
"""
Expand Down Expand Up @@ -1395,20 +1394,20 @@ def counts(self, flow: bool = False) -> np.typing.NDArray[Any]:
view: Any = self.view(flow)

if len(view.dtype) == 0:
return view # type: ignore[no-any-return]
return view

if hasattr(view, "sum_of_weights"):
return np.divide( # type: ignore[no-any-return]
return np.divide(
view.sum_of_weights**2,
view.sum_of_weights_squared,
out=np.zeros_like(view.sum_of_weights, dtype=np.float64),
where=view.sum_of_weights_squared != 0,
)

if hasattr(view, "count"):
return view.count # type: ignore[no-any-return]
return view.count

return view.value # type: ignore[no-any-return]
return view.value


if TYPE_CHECKING:
Expand Down