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.
Files changed (115) hide show
  1. benchmarks/__init__.py +1 -0
  2. benchmarks/__main__.py +6 -0
  3. benchmarks/_baseline.py +171 -0
  4. benchmarks/_bitwise.py +231 -0
  5. benchmarks/_complex.py +176 -0
  6. benchmarks/_contractions.py +291 -0
  7. benchmarks/_fft.py +198 -0
  8. benchmarks/_impl_urls.py +139 -0
  9. benchmarks/_linalg.py +197 -0
  10. benchmarks/_linalg_delegates.py +407 -0
  11. benchmarks/_metadata.py +141 -0
  12. benchmarks/_misc.py +653 -0
  13. benchmarks/_perf.py +321 -0
  14. benchmarks/_perm_group_calibration.py +175 -0
  15. benchmarks/_pointwise.py +372 -0
  16. benchmarks/_polynomial.py +193 -0
  17. benchmarks/_random.py +209 -0
  18. benchmarks/_reductions.py +136 -0
  19. benchmarks/_sorting.py +289 -0
  20. benchmarks/_stats.py +137 -0
  21. benchmarks/_window.py +92 -0
  22. benchmarks/accumulation/__init__.py +0 -0
  23. benchmarks/accumulation/bench_cost_compute.py +138 -0
  24. benchmarks/dashboard.py +312 -0
  25. benchmarks/runner.py +636 -0
  26. flopscope/__init__.py +273 -0
  27. flopscope/_accumulation/__init__.py +13 -0
  28. flopscope/_accumulation/_bipartite.py +121 -0
  29. flopscope/_accumulation/_burnside.py +51 -0
  30. flopscope/_accumulation/_cache.py +146 -0
  31. flopscope/_accumulation/_components.py +153 -0
  32. flopscope/_accumulation/_cost.py +1414 -0
  33. flopscope/_accumulation/_cost_descriptions.py +63 -0
  34. flopscope/_accumulation/_detection.py +318 -0
  35. flopscope/_accumulation/_ladder.py +191 -0
  36. flopscope/_accumulation/_output_orbit.py +104 -0
  37. flopscope/_accumulation/_partition.py +290 -0
  38. flopscope/_accumulation/_path_info.py +211 -0
  39. flopscope/_accumulation/_public.py +169 -0
  40. flopscope/_accumulation/_reduction.py +310 -0
  41. flopscope/_accumulation/_regimes.py +303 -0
  42. flopscope/_accumulation/_shape.py +33 -0
  43. flopscope/_accumulation/_wreath.py +209 -0
  44. flopscope/_budget.py +1027 -0
  45. flopscope/_config.py +118 -0
  46. flopscope/_counting_ops.py +451 -0
  47. flopscope/_display.py +478 -0
  48. flopscope/_docstrings.py +59 -0
  49. flopscope/_dtypes.py +20 -0
  50. flopscope/_einsum.py +717 -0
  51. flopscope/_errstate.py +25 -0
  52. flopscope/_flops.py +282 -0
  53. flopscope/_free_ops.py +2654 -0
  54. flopscope/_ndarray.py +1126 -0
  55. flopscope/_opt_einsum/LICENSE +21 -0
  56. flopscope/_opt_einsum/NOTICE +59 -0
  57. flopscope/_opt_einsum/__init__.py +209 -0
  58. flopscope/_opt_einsum/_contract.py +1478 -0
  59. flopscope/_opt_einsum/_helpers.py +164 -0
  60. flopscope/_opt_einsum/_hsluv.py +273 -0
  61. flopscope/_opt_einsum/_path_random.py +462 -0
  62. flopscope/_opt_einsum/_paths.py +1653 -0
  63. flopscope/_opt_einsum/_subgraph_symmetry.py +544 -0
  64. flopscope/_opt_einsum/_symmetry.py +140 -0
  65. flopscope/_opt_einsum/_typing.py +37 -0
  66. flopscope/_perm_group.py +717 -0
  67. flopscope/_pointwise.py +2522 -0
  68. flopscope/_polynomial.py +278 -0
  69. flopscope/_registry.py +3216 -0
  70. flopscope/_sorting_ops.py +571 -0
  71. flopscope/_symmetric.py +812 -0
  72. flopscope/_symmetry_transport.py +510 -0
  73. flopscope/_symmetry_utils.py +669 -0
  74. flopscope/_type_info.py +12 -0
  75. flopscope/_unwrap.py +70 -0
  76. flopscope/_validation.py +83 -0
  77. flopscope/_version_check.py +46 -0
  78. flopscope/_weights.py +195 -0
  79. flopscope/_window.py +177 -0
  80. flopscope/accounting.py +565 -0
  81. flopscope/data/default_weights.json +462 -0
  82. flopscope/data/weights.csv +509 -0
  83. flopscope/errors.py +197 -0
  84. flopscope/numpy/__init__.py +878 -0
  85. flopscope/numpy/fft/__init__.py +55 -0
  86. flopscope/numpy/fft/_free.py +51 -0
  87. flopscope/numpy/fft/_transforms.py +695 -0
  88. flopscope/numpy/linalg/__init__.py +105 -0
  89. flopscope/numpy/linalg/_aliases.py +126 -0
  90. flopscope/numpy/linalg/_compound.py +161 -0
  91. flopscope/numpy/linalg/_decompositions.py +353 -0
  92. flopscope/numpy/linalg/_properties.py +533 -0
  93. flopscope/numpy/linalg/_solvers.py +444 -0
  94. flopscope/numpy/linalg/_svd.py +122 -0
  95. flopscope/numpy/random/__init__.py +684 -0
  96. flopscope/numpy/random/_cost_formulas.py +115 -0
  97. flopscope/numpy/random/_counted_classes.py +241 -0
  98. flopscope/numpy/testing/__init__.py +13 -0
  99. flopscope/numpy/typing/__init__.py +30 -0
  100. flopscope/py.typed +0 -0
  101. flopscope/stats/__init__.py +84 -0
  102. flopscope/stats/_base.py +77 -0
  103. flopscope/stats/_cauchy.py +146 -0
  104. flopscope/stats/_erf.py +190 -0
  105. flopscope/stats/_expon.py +146 -0
  106. flopscope/stats/_laplace.py +150 -0
  107. flopscope/stats/_logistic.py +148 -0
  108. flopscope/stats/_lognorm.py +160 -0
  109. flopscope/stats/_ndtri.py +133 -0
  110. flopscope/stats/_norm.py +149 -0
  111. flopscope/stats/_truncnorm.py +186 -0
  112. flopscope/stats/_uniform.py +141 -0
  113. flopscope-0.2.0.dist-info/METADATA +23 -0
  114. flopscope-0.2.0.dist-info/RECORD +115 -0
  115. 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__])