flopscope 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchmarks/__init__.py +1 -0
- benchmarks/__main__.py +6 -0
- benchmarks/_baseline.py +171 -0
- benchmarks/_bitwise.py +231 -0
- benchmarks/_complex.py +176 -0
- benchmarks/_contractions.py +291 -0
- benchmarks/_fft.py +198 -0
- benchmarks/_impl_urls.py +139 -0
- benchmarks/_linalg.py +197 -0
- benchmarks/_linalg_delegates.py +407 -0
- benchmarks/_metadata.py +141 -0
- benchmarks/_misc.py +653 -0
- benchmarks/_perf.py +321 -0
- benchmarks/_perm_group_calibration.py +175 -0
- benchmarks/_pointwise.py +372 -0
- benchmarks/_polynomial.py +193 -0
- benchmarks/_random.py +209 -0
- benchmarks/_reductions.py +136 -0
- benchmarks/_sorting.py +289 -0
- benchmarks/_stats.py +137 -0
- benchmarks/_window.py +92 -0
- benchmarks/accumulation/__init__.py +0 -0
- benchmarks/accumulation/bench_cost_compute.py +138 -0
- benchmarks/dashboard.py +312 -0
- benchmarks/runner.py +636 -0
- flopscope/__init__.py +273 -0
- flopscope/_accumulation/__init__.py +13 -0
- flopscope/_accumulation/_bipartite.py +121 -0
- flopscope/_accumulation/_burnside.py +51 -0
- flopscope/_accumulation/_cache.py +146 -0
- flopscope/_accumulation/_components.py +153 -0
- flopscope/_accumulation/_cost.py +1414 -0
- flopscope/_accumulation/_cost_descriptions.py +63 -0
- flopscope/_accumulation/_detection.py +318 -0
- flopscope/_accumulation/_ladder.py +191 -0
- flopscope/_accumulation/_output_orbit.py +104 -0
- flopscope/_accumulation/_partition.py +290 -0
- flopscope/_accumulation/_path_info.py +211 -0
- flopscope/_accumulation/_public.py +169 -0
- flopscope/_accumulation/_reduction.py +310 -0
- flopscope/_accumulation/_regimes.py +303 -0
- flopscope/_accumulation/_shape.py +33 -0
- flopscope/_accumulation/_wreath.py +209 -0
- flopscope/_budget.py +1027 -0
- flopscope/_config.py +118 -0
- flopscope/_counting_ops.py +451 -0
- flopscope/_display.py +478 -0
- flopscope/_docstrings.py +59 -0
- flopscope/_dtypes.py +20 -0
- flopscope/_einsum.py +717 -0
- flopscope/_errstate.py +25 -0
- flopscope/_flops.py +282 -0
- flopscope/_free_ops.py +2654 -0
- flopscope/_ndarray.py +1126 -0
- flopscope/_opt_einsum/LICENSE +21 -0
- flopscope/_opt_einsum/NOTICE +59 -0
- flopscope/_opt_einsum/__init__.py +209 -0
- flopscope/_opt_einsum/_contract.py +1478 -0
- flopscope/_opt_einsum/_helpers.py +164 -0
- flopscope/_opt_einsum/_hsluv.py +273 -0
- flopscope/_opt_einsum/_path_random.py +462 -0
- flopscope/_opt_einsum/_paths.py +1653 -0
- flopscope/_opt_einsum/_subgraph_symmetry.py +544 -0
- flopscope/_opt_einsum/_symmetry.py +140 -0
- flopscope/_opt_einsum/_typing.py +37 -0
- flopscope/_perm_group.py +717 -0
- flopscope/_pointwise.py +2522 -0
- flopscope/_polynomial.py +278 -0
- flopscope/_registry.py +3216 -0
- flopscope/_sorting_ops.py +571 -0
- flopscope/_symmetric.py +812 -0
- flopscope/_symmetry_transport.py +510 -0
- flopscope/_symmetry_utils.py +669 -0
- flopscope/_type_info.py +12 -0
- flopscope/_unwrap.py +70 -0
- flopscope/_validation.py +83 -0
- flopscope/_version_check.py +46 -0
- flopscope/_weights.py +195 -0
- flopscope/_window.py +177 -0
- flopscope/accounting.py +565 -0
- flopscope/data/default_weights.json +462 -0
- flopscope/data/weights.csv +509 -0
- flopscope/errors.py +197 -0
- flopscope/numpy/__init__.py +878 -0
- flopscope/numpy/fft/__init__.py +55 -0
- flopscope/numpy/fft/_free.py +51 -0
- flopscope/numpy/fft/_transforms.py +695 -0
- flopscope/numpy/linalg/__init__.py +105 -0
- flopscope/numpy/linalg/_aliases.py +126 -0
- flopscope/numpy/linalg/_compound.py +161 -0
- flopscope/numpy/linalg/_decompositions.py +353 -0
- flopscope/numpy/linalg/_properties.py +533 -0
- flopscope/numpy/linalg/_solvers.py +444 -0
- flopscope/numpy/linalg/_svd.py +122 -0
- flopscope/numpy/random/__init__.py +684 -0
- flopscope/numpy/random/_cost_formulas.py +115 -0
- flopscope/numpy/random/_counted_classes.py +241 -0
- flopscope/numpy/testing/__init__.py +13 -0
- flopscope/numpy/typing/__init__.py +30 -0
- flopscope/py.typed +0 -0
- flopscope/stats/__init__.py +84 -0
- flopscope/stats/_base.py +77 -0
- flopscope/stats/_cauchy.py +146 -0
- flopscope/stats/_erf.py +190 -0
- flopscope/stats/_expon.py +146 -0
- flopscope/stats/_laplace.py +150 -0
- flopscope/stats/_logistic.py +148 -0
- flopscope/stats/_lognorm.py +160 -0
- flopscope/stats/_ndtri.py +133 -0
- flopscope/stats/_norm.py +149 -0
- flopscope/stats/_truncnorm.py +186 -0
- flopscope/stats/_uniform.py +141 -0
- flopscope-0.2.0.dist-info/METADATA +23 -0
- flopscope-0.2.0.dist-info/RECORD +115 -0
- flopscope-0.2.0.dist-info/WHEEL +4 -0
flopscope/_config.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Global configuration for flopscope."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
_SETTINGS: dict[str, object] = {
|
|
6
|
+
"check_nan_inf": False,
|
|
7
|
+
"dimino_budget": 50_000,
|
|
8
|
+
"einsum_path_cache_size": 4096,
|
|
9
|
+
"partition_budget": 100_000,
|
|
10
|
+
"symmetry_warnings": True,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Validators for settings that require range/type checks.
|
|
14
|
+
# Each validator receives the proposed value and raises ValueError/TypeError
|
|
15
|
+
# if the value is invalid.
|
|
16
|
+
_VALIDATORS: dict[str, object] = {
|
|
17
|
+
"dimino_budget": lambda v: _require_non_negative_int("dimino_budget", v),
|
|
18
|
+
"partition_budget": lambda v: _require_non_negative_int("partition_budget", v),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _require_non_negative_int(name: str, value: object) -> None:
|
|
23
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
24
|
+
raise TypeError(
|
|
25
|
+
f"Setting {name!r} requires a non-negative int; got {type(value).__name__!r}"
|
|
26
|
+
)
|
|
27
|
+
if value < 0:
|
|
28
|
+
raise ValueError(f"Setting {name!r} requires a non-negative int; got {value!r}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def configure(**kwargs: object) -> None:
|
|
32
|
+
"""Update flopscope global settings.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
check_nan_inf : bool
|
|
37
|
+
If ``True``, scan every counted op's output for NaN/Inf values and
|
|
38
|
+
emit a :class:`~flopscope.errors.FlopscopeWarning` if any are found.
|
|
39
|
+
The scan is two full O(n) sweeps over the result and is attributed
|
|
40
|
+
to ``flopscope_overhead_time``, so it is off by default for
|
|
41
|
+
production scoring. Opt in when debugging an estimator that
|
|
42
|
+
produces NaN/Inf to identify the introducing op. Default ``False``.
|
|
43
|
+
dimino_budget : int
|
|
44
|
+
Maximum number of group elements during whole-expression G_pt closure.
|
|
45
|
+
Pathological declared-symmetry cases that exceed this budget fall back
|
|
46
|
+
to dense cost with a CostFallbackWarning. Default ``50_000`` — accepts
|
|
47
|
+
groups up to ``|S_8| = 40_320`` with a small margin; rejects ``|S_9|``
|
|
48
|
+
and larger. Tune via ``flops.configure(dimino_budget=...)``;
|
|
49
|
+
``benchmarks/_perm_group_calibration.py`` recommends a value for the
|
|
50
|
+
host machine.
|
|
51
|
+
einsum_path_cache_size : int
|
|
52
|
+
Maximum number of entries in the einsum path cache.
|
|
53
|
+
Changing this rebuilds the cache (old entries are discarded).
|
|
54
|
+
Default ``4096``.
|
|
55
|
+
partition_budget : int
|
|
56
|
+
Maximum number of typed partitions a single component may have before
|
|
57
|
+
the partitionCount regime refuses. Components exceeding this budget
|
|
58
|
+
fall back to the dense cost with a CostFallbackWarning. Default
|
|
59
|
+
``100_000`` (covers Bell(9)=21_147; Bell(10)=115_975 forces fallback).
|
|
60
|
+
Set to ``0`` to force fallback for any non-trivial component.
|
|
61
|
+
symmetry_warnings : bool
|
|
62
|
+
If ``False``, suppress :class:`~flopscope.errors.SymmetryLossWarning`
|
|
63
|
+
warnings. Default ``True``.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
None
|
|
68
|
+
Updates the in-process global configuration immediately.
|
|
69
|
+
|
|
70
|
+
Examples
|
|
71
|
+
--------
|
|
72
|
+
>>> import flopscope as flops
|
|
73
|
+
>>> flops.configure(einsum_path_cache_size=8192)
|
|
74
|
+
>>> flops.configure(symmetry_warnings=False)
|
|
75
|
+
>>> flops.configure(check_nan_inf=True)
|
|
76
|
+
>>> flops.configure(partition_budget=50_000)
|
|
77
|
+
>>> flops.configure(dimino_budget=1_000_000)
|
|
78
|
+
"""
|
|
79
|
+
for key, value in kwargs.items():
|
|
80
|
+
if key not in _SETTINGS:
|
|
81
|
+
raise ValueError(f"Unknown setting: {key!r}")
|
|
82
|
+
validator = _VALIDATORS.get(key)
|
|
83
|
+
if validator is not None:
|
|
84
|
+
validator(value) # type: ignore[call-arg]
|
|
85
|
+
_SETTINGS[key] = value
|
|
86
|
+
|
|
87
|
+
if "einsum_path_cache_size" in kwargs:
|
|
88
|
+
from flopscope._einsum import _rebuild_einsum_cache
|
|
89
|
+
|
|
90
|
+
_rebuild_einsum_cache()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_setting(key: str) -> object:
|
|
94
|
+
"""Return the current value of a global setting."""
|
|
95
|
+
return _SETTINGS[key]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def set_setting(key: str, value: object) -> None:
|
|
99
|
+
"""Set a single global setting by name.
|
|
100
|
+
|
|
101
|
+
Equivalent to ``configure(**{key: value})``. Provided for call-sites
|
|
102
|
+
that hold the setting name in a variable.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
key : str
|
|
107
|
+
The setting name.
|
|
108
|
+
value : object
|
|
109
|
+
The new value. Subject to the same validation as :func:`configure`.
|
|
110
|
+
|
|
111
|
+
Raises
|
|
112
|
+
------
|
|
113
|
+
ValueError
|
|
114
|
+
If *key* is unknown or the value fails range validation.
|
|
115
|
+
TypeError
|
|
116
|
+
If the value fails type validation.
|
|
117
|
+
"""
|
|
118
|
+
configure(**{key: value})
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Counted wrappers for trace, histogram, and generation operations.
|
|
2
|
+
|
|
3
|
+
These are operations that look "free" but involve genuine computation.
|
|
4
|
+
Each function charges a FLOP cost to the active budget.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import builtins as _builtins
|
|
10
|
+
import inspect as _inspect
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import numpy as _np
|
|
15
|
+
from numpy.typing import ArrayLike, DTypeLike
|
|
16
|
+
|
|
17
|
+
from flopscope._budget import _call_numpy, _counted_wrapper
|
|
18
|
+
from flopscope._docstrings import attach_docstring
|
|
19
|
+
from flopscope._flops import _ceil_log2
|
|
20
|
+
from flopscope._ndarray import FlopscopeArray, _to_base_ndarray, _to_base_ndarray_tree
|
|
21
|
+
from flopscope._validation import require_budget
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Reductions disguised as free
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@_counted_wrapper
|
|
29
|
+
def trace(
|
|
30
|
+
a: ArrayLike,
|
|
31
|
+
offset: int = 0,
|
|
32
|
+
axis1: int = 0,
|
|
33
|
+
axis2: int = 1,
|
|
34
|
+
dtype: DTypeLike | None = None,
|
|
35
|
+
out: FlopscopeArray | None = None,
|
|
36
|
+
) -> FlopscopeArray:
|
|
37
|
+
budget = require_budget()
|
|
38
|
+
a = _np.asarray(a)
|
|
39
|
+
cost = _builtins.max(_builtins.min(a.shape[axis1], a.shape[axis2]), 1)
|
|
40
|
+
out_stripped = _to_base_ndarray(out) if out is not None else None
|
|
41
|
+
with budget.deduct("trace", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
|
|
42
|
+
result = _call_numpy(
|
|
43
|
+
_np.trace,
|
|
44
|
+
_to_base_ndarray(a),
|
|
45
|
+
offset=offset,
|
|
46
|
+
axis1=axis1,
|
|
47
|
+
axis2=axis2,
|
|
48
|
+
dtype=dtype,
|
|
49
|
+
out=out_stripped,
|
|
50
|
+
)
|
|
51
|
+
return out if out is not None else result # type: ignore[return-value]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
attach_docstring(
|
|
55
|
+
trace, _np.trace, "counted_custom", "min(a.shape[axis1], a.shape[axis2]) FLOPs"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@_counted_wrapper
|
|
60
|
+
def allclose(a: ArrayLike, b: ArrayLike, **kwargs: Any) -> bool:
|
|
61
|
+
budget = require_budget()
|
|
62
|
+
a = _np.asarray(a)
|
|
63
|
+
b = _np.asarray(b)
|
|
64
|
+
out_shape = _np.broadcast_shapes(a.shape, b.shape)
|
|
65
|
+
numel = 1
|
|
66
|
+
for d in out_shape:
|
|
67
|
+
numel *= d
|
|
68
|
+
cost = _builtins.max(numel, 1)
|
|
69
|
+
with budget.deduct(
|
|
70
|
+
"allclose", flop_cost=cost, subscripts=None, shapes=(a.shape, b.shape)
|
|
71
|
+
):
|
|
72
|
+
result = _call_numpy(_np.allclose, a, b, **kwargs)
|
|
73
|
+
return result # type: ignore[return-value]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
attach_docstring(allclose, _np.allclose, "counted_custom", "numel(a) FLOPs")
|
|
77
|
+
allclose.__signature__ = _inspect.signature(_np.allclose) # pyright: ignore[reportFunctionMemberAccess]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@_counted_wrapper
|
|
81
|
+
def array_equal(a: ArrayLike, b: ArrayLike, **kwargs: Any) -> bool:
|
|
82
|
+
budget = require_budget()
|
|
83
|
+
a = _np.asarray(a)
|
|
84
|
+
b = _np.asarray(b)
|
|
85
|
+
# array_equal does not broadcast; returns False on shape mismatch
|
|
86
|
+
cost = _builtins.max(a.size, b.size, 1)
|
|
87
|
+
with budget.deduct(
|
|
88
|
+
"array_equal", flop_cost=cost, subscripts=None, shapes=(a.shape, b.shape)
|
|
89
|
+
):
|
|
90
|
+
result = _call_numpy(_np.array_equal, a, b, **kwargs)
|
|
91
|
+
return result # type: ignore[return-value]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
attach_docstring(array_equal, _np.array_equal, "counted_custom", "numel(a) FLOPs")
|
|
95
|
+
array_equal.__signature__ = _inspect.signature(_np.array_equal) # pyright: ignore[reportFunctionMemberAccess]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@_counted_wrapper
|
|
99
|
+
def array_equiv(a: ArrayLike, b: ArrayLike) -> bool:
|
|
100
|
+
budget = require_budget()
|
|
101
|
+
a = _np.asarray(a)
|
|
102
|
+
b = _np.asarray(b)
|
|
103
|
+
# array_equiv broadcasts; returns False if shapes are incompatible
|
|
104
|
+
try:
|
|
105
|
+
out_shape = _np.broadcast_shapes(a.shape, b.shape)
|
|
106
|
+
numel = 1
|
|
107
|
+
for d in out_shape:
|
|
108
|
+
numel *= d
|
|
109
|
+
cost = _builtins.max(numel, 1)
|
|
110
|
+
except ValueError:
|
|
111
|
+
cost = _builtins.max(a.size, b.size, 1)
|
|
112
|
+
with budget.deduct(
|
|
113
|
+
"array_equiv", flop_cost=cost, subscripts=None, shapes=(a.shape, b.shape)
|
|
114
|
+
):
|
|
115
|
+
result = _call_numpy(_np.array_equiv, a, b)
|
|
116
|
+
return result # type: ignore[return-value]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
attach_docstring(array_equiv, _np.array_equiv, "counted_custom", "numel(a) FLOPs")
|
|
120
|
+
array_equiv.__signature__ = _inspect.signature(_np.array_equiv) # pyright: ignore[reportFunctionMemberAccess]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# Histogram & counting
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@_counted_wrapper
|
|
129
|
+
def histogram(
|
|
130
|
+
a: ArrayLike,
|
|
131
|
+
bins: int | Sequence[int] | str = 10,
|
|
132
|
+
**kwargs: Any,
|
|
133
|
+
) -> tuple[FlopscopeArray, FlopscopeArray]:
|
|
134
|
+
budget = require_budget()
|
|
135
|
+
a = _np.asarray(a)
|
|
136
|
+
n = a.size
|
|
137
|
+
if isinstance(bins, _builtins.int):
|
|
138
|
+
cost = _builtins.max(n * _ceil_log2(bins), 1)
|
|
139
|
+
elif isinstance(bins, _builtins.str):
|
|
140
|
+
cost = _builtins.max(n, 1)
|
|
141
|
+
else:
|
|
142
|
+
bins_arr = _np.asarray(bins)
|
|
143
|
+
cost = _builtins.max(n * _ceil_log2(_builtins.len(bins_arr)), 1)
|
|
144
|
+
with budget.deduct("histogram", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
|
|
145
|
+
result = _call_numpy(_np.histogram, a, bins=bins, **kwargs)
|
|
146
|
+
return result # type: ignore[return-value]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
attach_docstring(
|
|
150
|
+
histogram,
|
|
151
|
+
_np.histogram,
|
|
152
|
+
"counted_custom",
|
|
153
|
+
"n * ceil(log2(bins)) FLOPs when bins is int; n FLOPs otherwise",
|
|
154
|
+
)
|
|
155
|
+
histogram.__signature__ = _inspect.signature(_np.histogram) # pyright: ignore[reportFunctionMemberAccess]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@_counted_wrapper
|
|
159
|
+
def histogram2d(
|
|
160
|
+
x: ArrayLike,
|
|
161
|
+
y: ArrayLike,
|
|
162
|
+
bins: Any = 10,
|
|
163
|
+
**kwargs: Any,
|
|
164
|
+
) -> tuple[FlopscopeArray, FlopscopeArray, FlopscopeArray]:
|
|
165
|
+
budget = require_budget()
|
|
166
|
+
x = _np.asarray(x)
|
|
167
|
+
y = _np.asarray(y)
|
|
168
|
+
n = x.size
|
|
169
|
+
|
|
170
|
+
# Determine bx, by
|
|
171
|
+
if isinstance(bins, _builtins.int):
|
|
172
|
+
cost = _builtins.max(n * (_ceil_log2(bins) + _ceil_log2(bins)), 1)
|
|
173
|
+
elif (
|
|
174
|
+
isinstance(bins, (_builtins.list, tuple))
|
|
175
|
+
and _builtins.len(bins) == 2
|
|
176
|
+
and isinstance(bins[0], _builtins.int)
|
|
177
|
+
and isinstance(bins[1], _builtins.int)
|
|
178
|
+
):
|
|
179
|
+
bx, by = bins[0], bins[1]
|
|
180
|
+
cost = _builtins.max(n * (_ceil_log2(bx) + _ceil_log2(by)), 1)
|
|
181
|
+
elif isinstance(bins, (_builtins.list, tuple)) and _builtins.len(bins) == 2:
|
|
182
|
+
b0 = _np.asarray(bins[0])
|
|
183
|
+
b1 = _np.asarray(bins[1])
|
|
184
|
+
if b0.ndim >= 1 and b1.ndim >= 1:
|
|
185
|
+
cost = _builtins.max(
|
|
186
|
+
n * (_ceil_log2(_builtins.len(b0)) + _ceil_log2(_builtins.len(b1))), 1
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
cost = _builtins.max(n, 1)
|
|
190
|
+
else:
|
|
191
|
+
cost = _builtins.max(n, 1)
|
|
192
|
+
|
|
193
|
+
with budget.deduct(
|
|
194
|
+
"histogram2d", flop_cost=cost, subscripts=None, shapes=(x.shape, y.shape)
|
|
195
|
+
):
|
|
196
|
+
result = _call_numpy(_np.histogram2d, x, y, bins=bins, **kwargs)
|
|
197
|
+
return result # type: ignore[return-value]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
attach_docstring(
|
|
201
|
+
histogram2d,
|
|
202
|
+
_np.histogram2d,
|
|
203
|
+
"counted_custom",
|
|
204
|
+
"n * (ceil(log2(bx)) + ceil(log2(by))) FLOPs when bins is int pair; n FLOPs otherwise",
|
|
205
|
+
)
|
|
206
|
+
histogram2d.__signature__ = _inspect.signature(_np.histogram2d) # pyright: ignore[reportFunctionMemberAccess]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@_counted_wrapper
|
|
210
|
+
def histogramdd(
|
|
211
|
+
sample: ArrayLike,
|
|
212
|
+
bins: Any = 10,
|
|
213
|
+
**kwargs: Any,
|
|
214
|
+
) -> tuple[FlopscopeArray, list[FlopscopeArray]]:
|
|
215
|
+
budget = require_budget()
|
|
216
|
+
sample = _np.asarray(sample)
|
|
217
|
+
# sample shape: (n, d) or (n,) for 1-d
|
|
218
|
+
if sample.ndim == 1:
|
|
219
|
+
n = sample.shape[0]
|
|
220
|
+
d = 1
|
|
221
|
+
else:
|
|
222
|
+
n, d = sample.shape[0], sample.shape[1]
|
|
223
|
+
|
|
224
|
+
if isinstance(bins, _builtins.int):
|
|
225
|
+
cost = _builtins.max(n * d * _ceil_log2(bins), 1)
|
|
226
|
+
elif isinstance(bins, (_builtins.list, tuple)):
|
|
227
|
+
total_log = 0
|
|
228
|
+
for b in bins:
|
|
229
|
+
if isinstance(b, _builtins.int):
|
|
230
|
+
total_log += _ceil_log2(b)
|
|
231
|
+
else:
|
|
232
|
+
b_arr = _np.asarray(b)
|
|
233
|
+
if b_arr.ndim >= 1 and b_arr.size > 0:
|
|
234
|
+
total_log += _ceil_log2(_builtins.len(b_arr))
|
|
235
|
+
else:
|
|
236
|
+
total_log += 1
|
|
237
|
+
cost = _builtins.max(n * total_log, 1)
|
|
238
|
+
else:
|
|
239
|
+
cost = _builtins.max(n, 1)
|
|
240
|
+
|
|
241
|
+
with budget.deduct(
|
|
242
|
+
"histogramdd", flop_cost=cost, subscripts=None, shapes=(sample.shape,)
|
|
243
|
+
):
|
|
244
|
+
result = _call_numpy(_np.histogramdd, sample, bins=bins, **kwargs)
|
|
245
|
+
return result # type: ignore[return-value]
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
attach_docstring(
|
|
249
|
+
histogramdd,
|
|
250
|
+
_np.histogramdd,
|
|
251
|
+
"counted_custom",
|
|
252
|
+
"n * d * ceil(log2(bins)) FLOPs when bins is int; n FLOPs otherwise",
|
|
253
|
+
)
|
|
254
|
+
histogramdd.__signature__ = _inspect.signature(_np.histogramdd) # pyright: ignore[reportFunctionMemberAccess]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@_counted_wrapper
|
|
258
|
+
def histogram_bin_edges(
|
|
259
|
+
a: ArrayLike,
|
|
260
|
+
bins: int | Sequence[int] | str = 10,
|
|
261
|
+
**kwargs: Any,
|
|
262
|
+
) -> FlopscopeArray:
|
|
263
|
+
budget = require_budget()
|
|
264
|
+
a = _np.asarray(a)
|
|
265
|
+
cost = _builtins.max(a.size, 1)
|
|
266
|
+
with budget.deduct(
|
|
267
|
+
"histogram_bin_edges", flop_cost=cost, subscripts=None, shapes=(a.shape,)
|
|
268
|
+
):
|
|
269
|
+
result = _call_numpy(_np.histogram_bin_edges, a, bins=bins, **kwargs)
|
|
270
|
+
return result # type: ignore[return-value]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
attach_docstring(
|
|
274
|
+
histogram_bin_edges, _np.histogram_bin_edges, "counted_custom", "numel(a) FLOPs"
|
|
275
|
+
)
|
|
276
|
+
histogram_bin_edges.__signature__ = _inspect.signature(_np.histogram_bin_edges) # pyright: ignore[reportFunctionMemberAccess]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@_counted_wrapper
|
|
280
|
+
def bincount(x: ArrayLike, **kwargs: Any) -> FlopscopeArray:
|
|
281
|
+
budget = require_budget()
|
|
282
|
+
x = _np.asarray(x)
|
|
283
|
+
cost = _builtins.max(x.size, 1)
|
|
284
|
+
with budget.deduct("bincount", flop_cost=cost, subscripts=None, shapes=(x.shape,)):
|
|
285
|
+
result = _call_numpy(_np.bincount, x, **kwargs)
|
|
286
|
+
return result # type: ignore[return-value]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
attach_docstring(bincount, _np.bincount, "counted_custom", "numel(x) FLOPs")
|
|
290
|
+
try:
|
|
291
|
+
bincount.__signature__ = _inspect.signature(_np.bincount) # pyright: ignore[reportFunctionMemberAccess]
|
|
292
|
+
except (ValueError, TypeError):
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ---------------------------------------------------------------------------
|
|
297
|
+
# Array generation with arithmetic
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@_counted_wrapper
|
|
302
|
+
def logspace(
|
|
303
|
+
start: Any,
|
|
304
|
+
stop: Any,
|
|
305
|
+
num: int = 50,
|
|
306
|
+
**kwargs: Any,
|
|
307
|
+
) -> FlopscopeArray:
|
|
308
|
+
budget = require_budget()
|
|
309
|
+
cost = _builtins.max(num, 1)
|
|
310
|
+
with budget.deduct("logspace", flop_cost=cost, subscripts=None, shapes=((num,),)):
|
|
311
|
+
result = _call_numpy(_np.logspace, start, stop, num=num, **kwargs)
|
|
312
|
+
return result # type: ignore[return-value]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
attach_docstring(logspace, _np.logspace, "counted_custom", "num FLOPs")
|
|
316
|
+
logspace.__signature__ = _inspect.signature(_np.logspace) # pyright: ignore[reportFunctionMemberAccess]
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@_counted_wrapper
|
|
320
|
+
def geomspace(
|
|
321
|
+
start: Any,
|
|
322
|
+
stop: Any,
|
|
323
|
+
num: int = 50,
|
|
324
|
+
**kwargs: Any,
|
|
325
|
+
) -> FlopscopeArray:
|
|
326
|
+
budget = require_budget()
|
|
327
|
+
cost = _builtins.max(num, 1)
|
|
328
|
+
with budget.deduct("geomspace", flop_cost=cost, subscripts=None, shapes=((num,),)):
|
|
329
|
+
result = _call_numpy(_np.geomspace, start, stop, num=num, **kwargs)
|
|
330
|
+
return result # type: ignore[return-value]
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
attach_docstring(geomspace, _np.geomspace, "counted_custom", "num FLOPs")
|
|
334
|
+
geomspace.__signature__ = _inspect.signature(_np.geomspace) # pyright: ignore[reportFunctionMemberAccess]
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@_counted_wrapper
|
|
338
|
+
def vander(
|
|
339
|
+
x: ArrayLike,
|
|
340
|
+
N: int | None = None,
|
|
341
|
+
**kwargs: Any,
|
|
342
|
+
) -> FlopscopeArray:
|
|
343
|
+
budget = require_budget()
|
|
344
|
+
x = _np.asarray(x)
|
|
345
|
+
n = _builtins.len(x)
|
|
346
|
+
if N is None:
|
|
347
|
+
N = n
|
|
348
|
+
cost = _builtins.max(n * (N - 1), 1)
|
|
349
|
+
with budget.deduct("vander", flop_cost=cost, subscripts=None, shapes=(x.shape,)):
|
|
350
|
+
result = _call_numpy(_np.vander, x, N=N, **kwargs)
|
|
351
|
+
return result # type: ignore[return-value]
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
attach_docstring(vander, _np.vander, "counted_custom", "len(x) * (N-1) FLOPs")
|
|
355
|
+
vander.__signature__ = _inspect.signature(_np.vander) # pyright: ignore[reportFunctionMemberAccess]
|
|
356
|
+
|
|
357
|
+
# ---------------------------------------------------------------------------
|
|
358
|
+
# Apply & piecewise (formerly blacklisted)
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@_counted_wrapper
|
|
363
|
+
def apply_along_axis(
|
|
364
|
+
func1d: Any,
|
|
365
|
+
axis: int,
|
|
366
|
+
arr: ArrayLike,
|
|
367
|
+
*args: Any,
|
|
368
|
+
**kwargs: Any,
|
|
369
|
+
) -> FlopscopeArray:
|
|
370
|
+
"""Counted version of np.apply_along_axis. Cost: numel(output)."""
|
|
371
|
+
budget = require_budget()
|
|
372
|
+
if not isinstance(arr, _np.ndarray):
|
|
373
|
+
arr = _np.asarray(arr)
|
|
374
|
+
result = _np.apply_along_axis(func1d, axis, _to_base_ndarray(arr), *args, **kwargs)
|
|
375
|
+
cost = result.size if hasattr(result, "size") else 1
|
|
376
|
+
with budget.deduct(
|
|
377
|
+
"apply_along_axis", flop_cost=cost, subscripts=None, shapes=(arr.shape,)
|
|
378
|
+
):
|
|
379
|
+
pass
|
|
380
|
+
return result # type: ignore[return-value]
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
attach_docstring(
|
|
384
|
+
apply_along_axis,
|
|
385
|
+
_np.apply_along_axis,
|
|
386
|
+
"counted_custom",
|
|
387
|
+
"numel(output) FLOPs",
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@_counted_wrapper
|
|
392
|
+
def apply_over_axes(
|
|
393
|
+
func: Any,
|
|
394
|
+
a: ArrayLike,
|
|
395
|
+
axes: int | Sequence[int],
|
|
396
|
+
) -> FlopscopeArray:
|
|
397
|
+
"""Counted version of np.apply_over_axes. Cost: numel(output)."""
|
|
398
|
+
budget = require_budget()
|
|
399
|
+
if not isinstance(a, _np.ndarray):
|
|
400
|
+
a = _np.asarray(a)
|
|
401
|
+
result = _np.apply_over_axes(func, _to_base_ndarray(a), axes)
|
|
402
|
+
cost = result.size if hasattr(result, "size") else 1
|
|
403
|
+
with budget.deduct(
|
|
404
|
+
"apply_over_axes", flop_cost=cost, subscripts=None, shapes=(a.shape,)
|
|
405
|
+
):
|
|
406
|
+
pass
|
|
407
|
+
return result # type: ignore[return-value]
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
attach_docstring(
|
|
411
|
+
apply_over_axes,
|
|
412
|
+
_np.apply_over_axes,
|
|
413
|
+
"counted_custom",
|
|
414
|
+
"numel(output) FLOPs",
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@_counted_wrapper
|
|
419
|
+
def piecewise(
|
|
420
|
+
x: ArrayLike,
|
|
421
|
+
condlist: Any,
|
|
422
|
+
funclist: Any,
|
|
423
|
+
*args: Any,
|
|
424
|
+
**kw: Any,
|
|
425
|
+
) -> FlopscopeArray:
|
|
426
|
+
"""Counted version of np.piecewise. Cost: numel(input)."""
|
|
427
|
+
budget = require_budget()
|
|
428
|
+
if not isinstance(x, _np.ndarray):
|
|
429
|
+
x = _np.asarray(x)
|
|
430
|
+
result = _np.piecewise(
|
|
431
|
+
_to_base_ndarray(x), _to_base_ndarray_tree(condlist), funclist, *args, **kw
|
|
432
|
+
)
|
|
433
|
+
cost = x.size
|
|
434
|
+
with budget.deduct("piecewise", flop_cost=cost, subscripts=None, shapes=(x.shape,)):
|
|
435
|
+
pass
|
|
436
|
+
return result # type: ignore[return-value]
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
attach_docstring(
|
|
440
|
+
piecewise,
|
|
441
|
+
_np.piecewise,
|
|
442
|
+
"counted_custom",
|
|
443
|
+
"numel(input) FLOPs",
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
import sys as _sys # noqa: E402
|
|
448
|
+
|
|
449
|
+
from flopscope._ndarray import wrap_module_returns as _wrap_module_returns # noqa: E402
|
|
450
|
+
|
|
451
|
+
_wrap_module_returns(_sys.modules[__name__])
|