CoolFace
Apppublic

bbqddt2/Antigravity

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes
_reductions_impl.py464 linesDownload Raw Back to root
1# mypy: ignore-errors
2
3"""Implementation of reduction operations, to be wrapped into arrays, dtypes etc
4in the 'public' layer.
5
6Anything here only deals with torch objects, e.g. "dtype" is a torch.dtype instance etc
7"""
8
9from __future__ import annotations
10
11import functools
12from typing import TYPE_CHECKING
13
14import torch
15
16from . import _dtypes_impl, _util
17
18
19if TYPE_CHECKING:
20    from ._normalizations import (
21        ArrayLike,
22        AxisLike,
23        DTypeLike,
24        KeepDims,
25        NotImplementedType,
26        OutArray,
27    )
28
29
30def _deco_axis_expand(func):
31    """
32    Generically handle axis arguments in reductions.
33    axis is *always* the 2nd arg in the function so no need to have a look at its signature
34    """
35
36    @functools.wraps(func)
37    def wrapped(a, axis=None, *args, **kwds):
38        if axis is not None:
39            axis = _util.normalize_axis_tuple(axis, a.ndim)
40
41        if axis == ():
42            # So we insert a length-one axis and run the reduction along it.
43            # We cannot return a.clone() as this would sidestep the checks inside the function
44            newshape = _util.expand_shape(a.shape, axis=0)
45            a = a.reshape(newshape)
46            axis = (0,)
47
48        return func(a, axis, *args, **kwds)
49
50    return wrapped
51
52
53def _atleast_float(dtype, other_dtype):
54    """Return a dtype that is real or complex floating-point.
55
56    For inputs that are boolean or integer dtypes, this returns the default
57    float dtype; inputs that are complex get converted to the default complex
58    dtype; real floating-point dtypes (`float*`) get passed through unchanged
59    """
60    if dtype is None:
61        dtype = other_dtype
62    if not (dtype.is_floating_point or dtype.is_complex):
63        return _dtypes_impl.default_dtypes().float_dtype
64    return dtype
65
66
67@_deco_axis_expand
68def count_nonzero(a: ArrayLike, axis: AxisLike = None, *, keepdims: KeepDims = False):
69    return a.count_nonzero(axis)
70
71
72@_deco_axis_expand
73def argmax(
74    a: ArrayLike,
75    axis: AxisLike = None,
76    out: OutArray | None = None,
77    *,
78    keepdims: KeepDims = False,
79):
80    if a.is_complex():
81        raise NotImplementedError(f"argmax with dtype={a.dtype}.")
82
83    axis = _util.allow_only_single_axis(axis)
84
85    if a.dtype == torch.bool:
86        # RuntimeError: "argmax_cpu" not implemented for 'Bool'
87        a = a.to(torch.uint8)
88
89    return torch.argmax(a, axis)
90
91
92@_deco_axis_expand
93def argmin(
94    a: ArrayLike,
95    axis: AxisLike = None,
96    out: OutArray | None = None,
97    *,
98    keepdims: KeepDims = False,
99):
100    if a.is_complex():
101        raise NotImplementedError(f"argmin with dtype={a.dtype}.")
102
103    axis = _util.allow_only_single_axis(axis)
104
105    if a.dtype == torch.bool:
106        # RuntimeError: "argmin_cpu" not implemented for 'Bool'
107        a = a.to(torch.uint8)
108
109    return torch.argmin(a, axis)
110
111
112@_deco_axis_expand
113def any(
114    a: ArrayLike,
115    axis: AxisLike = None,
116    out: OutArray | None = None,
117    keepdims: KeepDims = False,
118    *,
119    where: NotImplementedType = None,
120):
121    axis = _util.allow_only_single_axis(axis)
122    axis_kw = {} if axis is None else {"dim": axis}
123    return torch.any(a, **axis_kw)
124
125
126@_deco_axis_expand
127def all(
128    a: ArrayLike,
129    axis: AxisLike = None,
130    out: OutArray | None = None,
131    keepdims: KeepDims = False,
132    *,
133    where: NotImplementedType = None,
134):
135    axis = _util.allow_only_single_axis(axis)
136    axis_kw = {} if axis is None else {"dim": axis}
137    return torch.all(a, **axis_kw)
138
139
140@_deco_axis_expand
141def amax(
142    a: ArrayLike,
143    axis: AxisLike = None,
144    out: OutArray | None = None,
145    keepdims: KeepDims = False,
146    initial: NotImplementedType = None,
147    where: NotImplementedType = None,
148):
149    if a.is_complex():
150        raise NotImplementedError(f"amax with dtype={a.dtype}")
151
152    return a.amax(axis)
153
154
155max = amax
156
157
158@_deco_axis_expand
159def amin(
160    a: ArrayLike,
161    axis: AxisLike = None,
162    out: OutArray | None = None,
163    keepdims: KeepDims = False,
164    initial: NotImplementedType = None,
165    where: NotImplementedType = None,
166):
167    if a.is_complex():
168        raise NotImplementedError(f"amin with dtype={a.dtype}")
169
170    return a.amin(axis)
171
172
173min = amin
174
175
176@_deco_axis_expand
177def ptp(
178    a: ArrayLike,
179    axis: AxisLike = None,
180    out: OutArray | None = None,
181    keepdims: KeepDims = False,
182):
183    return a.amax(axis) - a.amin(axis)
184
185
186@_deco_axis_expand
187def sum(
188    a: ArrayLike,
189    axis: AxisLike = None,
190    dtype: DTypeLike | None = None,
191    out: OutArray | None = None,
192    keepdims: KeepDims = False,
193    initial: NotImplementedType = None,
194    where: NotImplementedType = None,
195):
196    if dtype is not None and not isinstance(dtype, torch.dtype):
197        raise AssertionError(
198            f"dtype must be None or a torch.dtype, got {type(dtype).__name__}"
199        )
200
201    if dtype == torch.bool:
202        dtype = _dtypes_impl.default_dtypes().int_dtype
203
204    axis_kw = {} if axis is None else {"dim": axis}
205    return a.sum(dtype=dtype, **axis_kw)
206
207
208@_deco_axis_expand
209def prod(
210    a: ArrayLike,
211    axis: AxisLike = None,
212    dtype: DTypeLike | None = None,
213    out: OutArray | None = None,
214    keepdims: KeepDims = False,
215    initial: NotImplementedType = None,
216    where: NotImplementedType = None,
217):
218    axis = _util.allow_only_single_axis(axis)
219
220    if dtype == torch.bool:
221        dtype = _dtypes_impl.default_dtypes().int_dtype
222
223    axis_kw = {} if axis is None else {"dim": axis}
224    return a.prod(dtype=dtype, **axis_kw)
225
226
227product = prod
228
229
230@_deco_axis_expand
231def mean(
232    a: ArrayLike,
233    axis: AxisLike = None,
234    dtype: DTypeLike | None = None,
235    out: OutArray | None = None,
236    keepdims: KeepDims = False,
237    *,
238    where: NotImplementedType = None,
239):
240    dtype = _atleast_float(dtype, a.dtype)
241
242    axis_kw = {} if axis is None else {"dim": axis}
243    result = a.mean(dtype=dtype, **axis_kw)
244
245    return result
246
247
248@_deco_axis_expand
249def std(
250    a: ArrayLike,
251    axis: AxisLike = None,
252    dtype: DTypeLike | None = None,
253    out: OutArray | None = None,
254    ddof=0,
255    keepdims: KeepDims = False,
256    *,
257    where: NotImplementedType = None,
258):
259    in_dtype = dtype
260    dtype = _atleast_float(dtype, a.dtype)
261    tensor = _util.cast_if_needed(a, dtype)
262    result = tensor.std(dim=axis, correction=ddof)
263    return _util.cast_if_needed(result, in_dtype)
264
265
266@_deco_axis_expand
267def var(
268    a: ArrayLike,
269    axis: AxisLike = None,
270    dtype: DTypeLike | None = None,
271    out: OutArray | None = None,
272    ddof=0,
273    keepdims: KeepDims = False,
274    *,
275    where: NotImplementedType = None,
276):
277    in_dtype = dtype
278    dtype = _atleast_float(dtype, a.dtype)
279    tensor = _util.cast_if_needed(a, dtype)
280    result = tensor.var(dim=axis, correction=ddof)
281    return _util.cast_if_needed(result, in_dtype)
282
283
284# cumsum / cumprod are almost reductions:
285#   1. no keepdims
286#   2. axis=None flattens
287
288
289def cumsum(
290    a: ArrayLike,
291    axis: AxisLike = None,
292    dtype: DTypeLike | None = None,
293    out: OutArray | None = None,
294):
295    if dtype == torch.bool:
296        dtype = _dtypes_impl.default_dtypes().int_dtype
297    if dtype is None:
298        dtype = a.dtype
299
300    (a,), axis = _util.axis_none_flatten(a, axis=axis)
301    axis = _util.normalize_axis_index(axis, a.ndim)
302
303    return a.cumsum(axis=axis, dtype=dtype)
304
305
306def cumprod(
307    a: ArrayLike,
308    axis: AxisLike = None,
309    dtype: DTypeLike | None = None,
310    out: OutArray | None = None,
311):
312    if dtype == torch.bool:
313        dtype = _dtypes_impl.default_dtypes().int_dtype
314    if dtype is None:
315        dtype = a.dtype
316
317    (a,), axis = _util.axis_none_flatten(a, axis=axis)
318    axis = _util.normalize_axis_index(axis, a.ndim)
319
320    return a.cumprod(axis=axis, dtype=dtype)
321
322
323cumproduct = cumprod
324
325
326def average(
327    a: ArrayLike,
328    axis=None,
329    weights: ArrayLike = None,
330    returned=False,
331    *,
332    keepdims=False,
333):
334    if weights is None:
335        result = mean(a, axis=axis)
336        wsum = torch.as_tensor(a.numel() / result.numel(), dtype=result.dtype)
337    else:
338        if not a.dtype.is_floating_point:
339            a = a.double()
340
341        # axis & weights
342        if a.shape != weights.shape:
343            if axis is None:
344                raise TypeError(
345                    "Axis must be specified when shapes of a and weights differ."
346                )
347            if weights.ndim != 1:
348                raise TypeError(
349                    "1D weights expected when shapes of a and weights differ."
350                )
351            if weights.shape[0] != a.shape[axis]:
352                raise ValueError(
353                    "Length of weights not compatible with specified axis."
354                )
355
356            # setup weight to broadcast along axis
357            weights = torch.broadcast_to(weights, (a.ndim - 1) * (1,) + weights.shape)
358            weights = weights.swapaxes(-1, axis)
359
360        # do the work
361        result_dtype = _dtypes_impl.result_type_impl(a, weights)
362        numerator = sum(a * weights, axis, dtype=result_dtype)
363        wsum = sum(weights, axis, dtype=result_dtype)
364        result = numerator / wsum
365
366    # We process keepdims manually because the decorator does not deal with variadic returns
367    if keepdims:
368        result = _util.apply_keepdims(result, axis, a.ndim)
369
370    if returned:
371        if wsum.shape != result.shape:
372            wsum = torch.broadcast_to(wsum, result.shape).clone()
373        return result, wsum
374    else:
375        return result
376
377
378# Not using deco_axis_expand as it assumes that axis is the second arg
379def quantile(
380    a: ArrayLike,
381    q: ArrayLike,
382    axis: AxisLike = None,
383    out: OutArray | None = None,
384    overwrite_input=False,
385    method="linear",
386    keepdims: KeepDims = False,
387    *,
388    interpolation: NotImplementedType = None,
389):
390    if overwrite_input:
391        # raise NotImplementedError("overwrite_input in quantile not implemented.")
392        # NumPy documents that `overwrite_input` MAY modify inputs:
393        # https://numpy.org/doc/stable/reference/generated/numpy.percentile.html#numpy-percentile
394        # Here we choose to work out-of-place because why not.
395        pass
396
397    if not a.dtype.is_floating_point:
398        dtype = _dtypes_impl.default_dtypes().float_dtype
399        a = a.to(dtype)
400
401    # edge case: torch.quantile only supports float32 and float64
402    if a.dtype == torch.float16:
403        a = a.to(torch.float32)
404
405    if axis is None:
406        a = a.flatten()
407        q = q.flatten()
408        axis = (0,)
409    else:
410        axis = _util.normalize_axis_tuple(axis, a.ndim)
411
412    # FIXME(Mario) Doesn't np.quantile accept a tuple?
413    # torch.quantile does accept a number. If we don't want to implement the tuple behaviour
414    # (it's deffo low prio) change `normalize_axis_tuple` into a normalize_axis index above.
415    axis = _util.allow_only_single_axis(axis)
416
417    q = _util.cast_if_needed(q, a.dtype)
418
419    return torch.quantile(a, q, axis=axis, interpolation=method)
420
421
422def percentile(
423    a: ArrayLike,
424    q: ArrayLike,
425    axis: AxisLike = None,
426    out: OutArray | None = None,
427    overwrite_input=False,
428    method="linear",
429    keepdims: KeepDims = False,
430    *,
431    interpolation: NotImplementedType = None,
432):
433    # np.percentile(float_tensor, 30) : q.dtype is int64 => q / 100.0 is float32
434    if _dtypes_impl.python_type_for_torch(q.dtype) is int:
435        q = q.to(_dtypes_impl.default_dtypes().float_dtype)
436    qq = q / 100.0
437
438    return quantile(
439        a,
440        qq,
441        axis=axis,
442        overwrite_input=overwrite_input,
443        method=method,
444        keepdims=keepdims,
445        interpolation=interpolation,
446    )
447
448
449def median(
450    a: ArrayLike,
451    axis=None,
452    out: OutArray | None = None,
453    overwrite_input=False,
454    keepdims: KeepDims = False,
455):
456    return quantile(
457        a,
458        torch.as_tensor(0.5),
459        axis=axis,
460        overwrite_input=overwrite_input,
461        out=out,
462        keepdims=keepdims,
463    )
464