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
@@ -0,0 +1,695 @@
1
+ # src/flopscope/fft/_transforms.py
2
+ """FFT transform wrappers with FLOP counting.
3
+
4
+ Cost model: 5 * N * log2(N) for complex DFT of length N (Cooley-Tukey radix-2).
5
+ Source: Cooley & Tukey (1965); Van Loan, "Computational Frameworks for the FFT" (1992), §1.4.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import math
11
+ from collections.abc import Sequence
12
+
13
+ import numpy as _np
14
+ from numpy.typing import ArrayLike
15
+
16
+ from flopscope._budget import _call_numpy, _counted_wrapper
17
+ from flopscope._docstrings import attach_docstring
18
+ from flopscope._ndarray import FlopscopeArray, _to_base_ndarray
19
+ from flopscope._validation import require_budget
20
+
21
+
22
+ def fft_cost(n: int) -> int:
23
+ """FLOP cost of a 1-D complex FFT.
24
+
25
+ Parameters
26
+ ----------
27
+ n : int
28
+ Transform length.
29
+
30
+ Returns
31
+ -------
32
+ int
33
+ Estimated FLOP count: 5 * n * ceil(log2(n)).
34
+
35
+ Notes
36
+ -----
37
+ Source: Cooley & Tukey (1965); Van Loan, *Computational Frameworks
38
+ for the FFT* (1992), §1.4. Assumes radix-2 Cooley-Tukey algorithm.
39
+ """
40
+ if n <= 1:
41
+ return 0
42
+ return 5 * n * math.ceil(math.log2(n))
43
+
44
+
45
+ def rfft_cost(n: int) -> int:
46
+ """FLOP cost of a 1-D real FFT.
47
+
48
+ Parameters
49
+ ----------
50
+ n : int
51
+ Input length (real-valued).
52
+
53
+ Returns
54
+ -------
55
+ int
56
+ Estimated FLOP count: 5 * (n // 2) * ceil(log2(n)).
57
+
58
+ Notes
59
+ -----
60
+ The real FFT exploits conjugate symmetry, roughly halving the work
61
+ compared to a full complex FFT.
62
+ """
63
+ if n <= 1:
64
+ return 0
65
+ return 5 * (n // 2) * math.ceil(math.log2(n))
66
+
67
+
68
+ def fftn_cost(shape: tuple[int, ...]) -> int:
69
+ """FLOP cost of an N-D complex FFT.
70
+
71
+ Parameters
72
+ ----------
73
+ shape : tuple of int
74
+ Transform shape along each axis.
75
+
76
+ Returns
77
+ -------
78
+ int
79
+ Estimated FLOP count: 5 * N * sum(ceil(log2(d_i))), where N = prod(shape).
80
+
81
+ Notes
82
+ -----
83
+ The multi-dimensional FFT is computed as successive 1-D FFTs along each
84
+ axis. The cost along axis *i* is 5 * (N / d_i) * d_i * ceil(log2(d_i))
85
+ = 5 * N * ceil(log2(d_i)). Summing over axes gives
86
+ 5 * N * sum(ceil(log2(d_i))).
87
+ """
88
+ N = 1
89
+ for d in shape:
90
+ N *= d
91
+ if N <= 1:
92
+ return 0
93
+ log_sum = sum(math.ceil(math.log2(d)) for d in shape if d > 1)
94
+ return 5 * N * log_sum
95
+
96
+
97
+ def rfftn_cost(shape: tuple[int, ...]) -> int:
98
+ """FLOP cost of an N-D real FFT.
99
+
100
+ Parameters
101
+ ----------
102
+ shape : tuple of int
103
+ Transform shape along each axis.
104
+
105
+ Returns
106
+ -------
107
+ int
108
+ Estimated FLOP count: 5 * (N // 2) * sum(ceil(log2(d_i))), where N = prod(shape).
109
+
110
+ Notes
111
+ -----
112
+ Exploits conjugate symmetry along the last axis, roughly halving
113
+ the work compared to a full complex N-D FFT.
114
+ """
115
+ N = 1
116
+ for d in shape:
117
+ N *= d
118
+ if N <= 1:
119
+ return 0
120
+ log_sum = sum(math.ceil(math.log2(d)) for d in shape if d > 1)
121
+ return 5 * (N // 2) * log_sum
122
+
123
+
124
+ def hfft_cost(n_out: int) -> int:
125
+ """FLOP cost of a Hermitian FFT.
126
+
127
+ Parameters
128
+ ----------
129
+ n_out : int
130
+ Output length.
131
+
132
+ Returns
133
+ -------
134
+ int
135
+ Estimated FLOP count: 5 * n_out * ceil(log2(n_out)).
136
+
137
+ Notes
138
+ -----
139
+ The Hermitian FFT computes the FFT of a signal with Hermitian symmetry,
140
+ producing real-valued output.
141
+ """
142
+ if n_out <= 1:
143
+ return 0
144
+ return 5 * n_out * math.ceil(math.log2(n_out))
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Batch helpers
149
+ # ---------------------------------------------------------------------------
150
+
151
+
152
+ def _batch_count_1d(a: _np.ndarray, axis: int) -> int:
153
+ """Number of independent 1-D transforms along *axis*."""
154
+ if a.ndim == 0 or a.shape[axis] == 0:
155
+ return 1
156
+ return a.size // a.shape[axis]
157
+
158
+
159
+ def _batch_count_nd(a: _np.ndarray, axes: tuple[int, ...] | None) -> int:
160
+ """Number of independent N-D transforms over *axes*."""
161
+ if axes is None:
162
+ return 1 # all axes are transform axes
163
+ batch = a.size
164
+ for ax in axes:
165
+ batch //= a.shape[ax]
166
+ return max(batch, 1)
167
+
168
+
169
+ # 1-D transforms
170
+ @_counted_wrapper
171
+ def fft(
172
+ a: ArrayLike,
173
+ n: int | None = None,
174
+ axis: int = -1,
175
+ norm: str | None = None,
176
+ out: ArrayLike | None = None,
177
+ ) -> FlopscopeArray:
178
+ budget = require_budget()
179
+ if not isinstance(a, _np.ndarray):
180
+ a = _np.asarray(a)
181
+ if n is None:
182
+ n = a.shape[axis]
183
+ cost = _batch_count_1d(a, axis) * fft_cost(n)
184
+ with budget.deduct("fft.fft", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
185
+ result = _call_numpy(
186
+ _np.fft.fft,
187
+ _to_base_ndarray(a),
188
+ n=n,
189
+ axis=axis,
190
+ norm=norm, # type: ignore[reportArgumentType]
191
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
192
+ )
193
+ return out if out is not None else result # type: ignore[reportReturnType]
194
+
195
+
196
+ attach_docstring(
197
+ fft, _np.fft.fft, "fft", "5 \u00d7 n \u00d7 \u2308log\u2082(n)\u2309 FLOPs"
198
+ )
199
+
200
+
201
+ @_counted_wrapper
202
+ def ifft(
203
+ a: ArrayLike,
204
+ n: int | None = None,
205
+ axis: int = -1,
206
+ norm: str | None = None,
207
+ out: ArrayLike | None = None,
208
+ ) -> FlopscopeArray:
209
+ budget = require_budget()
210
+ if not isinstance(a, _np.ndarray):
211
+ a = _np.asarray(a)
212
+ if n is None:
213
+ n = a.shape[axis]
214
+ cost = _batch_count_1d(a, axis) * fft_cost(n)
215
+ with budget.deduct("fft.ifft", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
216
+ result = _call_numpy(
217
+ _np.fft.ifft,
218
+ _to_base_ndarray(a),
219
+ n=n,
220
+ axis=axis,
221
+ norm=norm, # type: ignore[reportArgumentType]
222
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
223
+ )
224
+ return out if out is not None else result # type: ignore[reportReturnType]
225
+
226
+
227
+ attach_docstring(
228
+ ifft, _np.fft.ifft, "fft", "5 \u00d7 n \u00d7 \u2308log\u2082(n)\u2309 FLOPs"
229
+ )
230
+
231
+
232
+ @_counted_wrapper
233
+ def rfft(
234
+ a: ArrayLike,
235
+ n: int | None = None,
236
+ axis: int = -1,
237
+ norm: str | None = None,
238
+ out: ArrayLike | None = None,
239
+ ) -> FlopscopeArray:
240
+ budget = require_budget()
241
+ if not isinstance(a, _np.ndarray):
242
+ a = _np.asarray(a)
243
+ if n is None:
244
+ n = a.shape[axis]
245
+ cost = _batch_count_1d(a, axis) * rfft_cost(n)
246
+ with budget.deduct("fft.rfft", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
247
+ result = _call_numpy(
248
+ _np.fft.rfft,
249
+ _to_base_ndarray(a),
250
+ n=n,
251
+ axis=axis,
252
+ norm=norm, # type: ignore[reportArgumentType]
253
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
254
+ )
255
+ return out if out is not None else result # type: ignore[reportReturnType]
256
+
257
+
258
+ attach_docstring(
259
+ rfft, _np.fft.rfft, "fft", "5 \u00d7 (n/2) \u00d7 \u2308log\u2082(n)\u2309 FLOPs"
260
+ )
261
+
262
+
263
+ @_counted_wrapper
264
+ def irfft(
265
+ a: ArrayLike,
266
+ n: int | None = None,
267
+ axis: int = -1,
268
+ norm: str | None = None,
269
+ out: ArrayLike | None = None,
270
+ ) -> FlopscopeArray:
271
+ budget = require_budget()
272
+ if not isinstance(a, _np.ndarray):
273
+ a = _np.asarray(a)
274
+ if n is None:
275
+ n = 2 * (a.shape[axis] - 1)
276
+ cost = _batch_count_1d(a, axis) * rfft_cost(n)
277
+ with budget.deduct("fft.irfft", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
278
+ result = _call_numpy(
279
+ _np.fft.irfft,
280
+ _to_base_ndarray(a),
281
+ n=n,
282
+ axis=axis,
283
+ norm=norm, # type: ignore[reportArgumentType]
284
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
285
+ )
286
+ return out if out is not None else result # type: ignore[reportReturnType]
287
+
288
+
289
+ attach_docstring(
290
+ irfft, _np.fft.irfft, "fft", "5 \u00d7 (n/2) \u00d7 \u2308log\u2082(n)\u2309 FLOPs"
291
+ )
292
+
293
+
294
+ # 2-D transforms
295
+ @_counted_wrapper
296
+ def fft2(
297
+ a: ArrayLike,
298
+ s: Sequence[int] | None = None,
299
+ axes: Sequence[int] = (-2, -1),
300
+ norm: str | None = None,
301
+ out: ArrayLike | None = None,
302
+ ) -> FlopscopeArray:
303
+ budget = require_budget()
304
+ if not isinstance(a, _np.ndarray):
305
+ a = _np.asarray(a)
306
+ if s is None:
307
+ s_for_cost = tuple(a.shape[ax] for ax in axes)
308
+ else:
309
+ s_for_cost = tuple(
310
+ a.shape[axes[i]] if si is None else si for i, si in enumerate(s)
311
+ )
312
+ cost = _batch_count_nd(a, axes) * fftn_cost(s_for_cost) # type: ignore[reportArgumentType]
313
+ with budget.deduct("fft.fft2", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
314
+ result = _call_numpy(
315
+ _np.fft.fft2,
316
+ _to_base_ndarray(a),
317
+ s=s,
318
+ axes=axes,
319
+ norm=norm, # type: ignore[reportArgumentType]
320
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
321
+ )
322
+ return out if out is not None else result # type: ignore[reportReturnType]
323
+
324
+
325
+ attach_docstring(
326
+ fft2,
327
+ _np.fft.fft2,
328
+ "fft",
329
+ "5 \u00d7 N \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
330
+ )
331
+
332
+
333
+ @_counted_wrapper
334
+ def ifft2(
335
+ a: ArrayLike,
336
+ s: Sequence[int] | None = None,
337
+ axes: Sequence[int] = (-2, -1),
338
+ norm: str | None = None,
339
+ out: ArrayLike | None = None,
340
+ ) -> FlopscopeArray:
341
+ budget = require_budget()
342
+ if not isinstance(a, _np.ndarray):
343
+ a = _np.asarray(a)
344
+ if s is None:
345
+ s_for_cost = tuple(a.shape[ax] for ax in axes)
346
+ else:
347
+ s_for_cost = tuple(
348
+ a.shape[axes[i]] if si is None else si for i, si in enumerate(s)
349
+ )
350
+ cost = _batch_count_nd(a, axes) * fftn_cost(s_for_cost) # type: ignore[reportArgumentType]
351
+ with budget.deduct("fft.ifft2", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
352
+ result = _call_numpy(
353
+ _np.fft.ifft2,
354
+ _to_base_ndarray(a),
355
+ s=s,
356
+ axes=axes,
357
+ norm=norm, # type: ignore[reportArgumentType]
358
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
359
+ )
360
+ return out if out is not None else result # type: ignore[reportReturnType]
361
+
362
+
363
+ attach_docstring(
364
+ ifft2,
365
+ _np.fft.ifft2,
366
+ "fft",
367
+ "5 \u00d7 N \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
368
+ )
369
+
370
+
371
+ @_counted_wrapper
372
+ def rfft2(
373
+ a: ArrayLike,
374
+ s: Sequence[int] | None = None,
375
+ axes: Sequence[int] = (-2, -1),
376
+ norm: str | None = None,
377
+ out: ArrayLike | None = None,
378
+ ) -> FlopscopeArray:
379
+ budget = require_budget()
380
+ if not isinstance(a, _np.ndarray):
381
+ a = _np.asarray(a)
382
+ if s is None:
383
+ s_for_cost = tuple(a.shape[ax] for ax in axes)
384
+ else:
385
+ s_for_cost = tuple(
386
+ a.shape[axes[i]] if si is None else si for i, si in enumerate(s)
387
+ )
388
+ cost = _batch_count_nd(a, axes) * rfftn_cost(s_for_cost) # type: ignore[reportArgumentType]
389
+ with budget.deduct("fft.rfft2", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
390
+ result = _call_numpy(
391
+ _np.fft.rfft2,
392
+ _to_base_ndarray(a),
393
+ s=s,
394
+ axes=axes,
395
+ norm=norm, # type: ignore[reportArgumentType]
396
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
397
+ )
398
+ return out if out is not None else result # type: ignore[reportReturnType]
399
+
400
+
401
+ attach_docstring(
402
+ rfft2,
403
+ _np.fft.rfft2,
404
+ "fft",
405
+ "5 \u00d7 (N/2) \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
406
+ )
407
+
408
+
409
+ @_counted_wrapper
410
+ def irfft2(
411
+ a: ArrayLike,
412
+ s: Sequence[int] | None = None,
413
+ axes: Sequence[int] = (-2, -1),
414
+ norm: str | None = None,
415
+ out: ArrayLike | None = None,
416
+ ) -> FlopscopeArray:
417
+ budget = require_budget()
418
+ if not isinstance(a, _np.ndarray):
419
+ a = _np.asarray(a)
420
+ if s is None:
421
+ s_for_cost = (a.shape[axes[0]], 2 * (a.shape[axes[1]] - 1))
422
+ else:
423
+ s_for_cost = tuple(
424
+ (a.shape[axes[i]] if i < len(s) - 1 else 2 * (a.shape[axes[i]] - 1))
425
+ if si is None
426
+ else si
427
+ for i, si in enumerate(s)
428
+ )
429
+ cost = _batch_count_nd(a, axes) * rfftn_cost(s_for_cost) # type: ignore[reportArgumentType]
430
+ with budget.deduct(
431
+ "fft.irfft2", flop_cost=cost, subscripts=None, shapes=(a.shape,)
432
+ ):
433
+ result = _call_numpy(
434
+ _np.fft.irfft2,
435
+ _to_base_ndarray(a),
436
+ s=s,
437
+ axes=axes,
438
+ norm=norm, # type: ignore[reportArgumentType]
439
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
440
+ )
441
+ return out if out is not None else result # type: ignore[reportReturnType]
442
+
443
+
444
+ attach_docstring(
445
+ irfft2,
446
+ _np.fft.irfft2,
447
+ "fft",
448
+ "5 \u00d7 (N/2) \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
449
+ )
450
+
451
+
452
+ # N-D transforms
453
+ @_counted_wrapper
454
+ def fftn(
455
+ a: ArrayLike,
456
+ s: Sequence[int] | None = None,
457
+ axes: Sequence[int] | None = None,
458
+ norm: str | None = None,
459
+ out: ArrayLike | None = None,
460
+ ) -> FlopscopeArray:
461
+ budget = require_budget()
462
+ if not isinstance(a, _np.ndarray):
463
+ a = _np.asarray(a)
464
+ if s is None:
465
+ s_for_cost = a.shape if axes is None else tuple(a.shape[ax] for ax in axes)
466
+ else:
467
+ eff_axes = tuple(range(a.ndim)) if axes is None else tuple(axes)
468
+ s_for_cost = tuple(
469
+ a.shape[eff_axes[i]] if si is None else si for i, si in enumerate(s)
470
+ )
471
+ cost = _batch_count_nd(a, axes) * fftn_cost(s_for_cost) # type: ignore[reportArgumentType]
472
+ with budget.deduct("fft.fftn", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
473
+ result = _call_numpy(
474
+ _np.fft.fftn,
475
+ _to_base_ndarray(a),
476
+ s=s,
477
+ axes=axes,
478
+ norm=norm, # type: ignore[reportArgumentType]
479
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
480
+ )
481
+ return out if out is not None else result # type: ignore[reportReturnType]
482
+
483
+
484
+ attach_docstring(
485
+ fftn,
486
+ _np.fft.fftn,
487
+ "fft",
488
+ "5 \u00d7 N \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
489
+ )
490
+
491
+
492
+ @_counted_wrapper
493
+ def ifftn(
494
+ a: ArrayLike,
495
+ s: Sequence[int] | None = None,
496
+ axes: Sequence[int] | None = None,
497
+ norm: str | None = None,
498
+ out: ArrayLike | None = None,
499
+ ) -> FlopscopeArray:
500
+ budget = require_budget()
501
+ if not isinstance(a, _np.ndarray):
502
+ a = _np.asarray(a)
503
+ if s is None:
504
+ s_for_cost = a.shape if axes is None else tuple(a.shape[ax] for ax in axes)
505
+ else:
506
+ eff_axes = tuple(range(a.ndim)) if axes is None else tuple(axes)
507
+ s_for_cost = tuple(
508
+ a.shape[eff_axes[i]] if si is None else si for i, si in enumerate(s)
509
+ )
510
+ cost = _batch_count_nd(a, axes) * fftn_cost(s_for_cost) # type: ignore[reportArgumentType]
511
+ with budget.deduct("fft.ifftn", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
512
+ result = _call_numpy(
513
+ _np.fft.ifftn,
514
+ _to_base_ndarray(a),
515
+ s=s,
516
+ axes=axes,
517
+ norm=norm, # type: ignore[reportArgumentType]
518
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
519
+ )
520
+ return out if out is not None else result # type: ignore[reportReturnType]
521
+
522
+
523
+ attach_docstring(
524
+ ifftn,
525
+ _np.fft.ifftn,
526
+ "fft",
527
+ "5 \u00d7 N \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
528
+ )
529
+
530
+
531
+ @_counted_wrapper
532
+ def rfftn(
533
+ a: ArrayLike,
534
+ s: Sequence[int] | None = None,
535
+ axes: Sequence[int] | None = None,
536
+ norm: str | None = None,
537
+ out: ArrayLike | None = None,
538
+ ) -> FlopscopeArray:
539
+ budget = require_budget()
540
+ if not isinstance(a, _np.ndarray):
541
+ a = _np.asarray(a)
542
+ if s is None:
543
+ s_for_cost = a.shape if axes is None else tuple(a.shape[ax] for ax in axes)
544
+ else:
545
+ eff_axes = tuple(range(a.ndim)) if axes is None else tuple(axes)
546
+ s_for_cost = tuple(
547
+ a.shape[eff_axes[i]] if si is None else si for i, si in enumerate(s)
548
+ )
549
+ cost = _batch_count_nd(a, axes) * rfftn_cost(s_for_cost) # type: ignore[reportArgumentType]
550
+ with budget.deduct("fft.rfftn", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
551
+ result = _call_numpy(
552
+ _np.fft.rfftn,
553
+ _to_base_ndarray(a),
554
+ s=s,
555
+ axes=axes,
556
+ norm=norm, # type: ignore[reportArgumentType]
557
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
558
+ )
559
+ return out if out is not None else result # type: ignore[reportReturnType]
560
+
561
+
562
+ attach_docstring(
563
+ rfftn,
564
+ _np.fft.rfftn,
565
+ "fft",
566
+ "5 \u00d7 (N/2) \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
567
+ )
568
+
569
+
570
+ @_counted_wrapper
571
+ def irfftn(
572
+ a: ArrayLike,
573
+ s: Sequence[int] | None = None,
574
+ axes: Sequence[int] | None = None,
575
+ norm: str | None = None,
576
+ out: ArrayLike | None = None,
577
+ ) -> FlopscopeArray:
578
+ budget = require_budget()
579
+ if not isinstance(a, _np.ndarray):
580
+ a = _np.asarray(a)
581
+ if s is None:
582
+ if axes is None:
583
+ s_for_cost = tuple(
584
+ d if i < len(a.shape) - 1 else 2 * (d - 1)
585
+ for i, d in enumerate(a.shape)
586
+ )
587
+ else:
588
+ s_for_cost = tuple(
589
+ a.shape[ax] if i < len(axes) - 1 else 2 * (a.shape[ax] - 1)
590
+ for i, ax in enumerate(axes)
591
+ )
592
+ else:
593
+ eff_axes = tuple(range(a.ndim)) if axes is None else tuple(axes)
594
+ s_for_cost = tuple(
595
+ (a.shape[eff_axes[i]] if i < len(s) - 1 else 2 * (a.shape[eff_axes[i]] - 1))
596
+ if si is None
597
+ else si
598
+ for i, si in enumerate(s)
599
+ )
600
+ cost = _batch_count_nd(a, axes) * rfftn_cost(s_for_cost) # type: ignore[reportArgumentType]
601
+ with budget.deduct(
602
+ "fft.irfftn", flop_cost=cost, subscripts=None, shapes=(a.shape,)
603
+ ):
604
+ result = _call_numpy(
605
+ _np.fft.irfftn,
606
+ _to_base_ndarray(a),
607
+ s=s,
608
+ axes=axes,
609
+ norm=norm, # type: ignore[reportArgumentType]
610
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
611
+ )
612
+ return out if out is not None else result # type: ignore[reportReturnType]
613
+
614
+
615
+ attach_docstring(
616
+ irfftn,
617
+ _np.fft.irfftn,
618
+ "fft",
619
+ "5 \u00d7 (N/2) \u00d7 \u2308log\u2082(N)\u2309 FLOPs, N = product of transform shape",
620
+ )
621
+
622
+
623
+ # Hermitian transforms
624
+ @_counted_wrapper
625
+ def hfft(
626
+ a: ArrayLike,
627
+ n: int | None = None,
628
+ axis: int = -1,
629
+ norm: str | None = None,
630
+ out: ArrayLike | None = None,
631
+ ) -> FlopscopeArray:
632
+ budget = require_budget()
633
+ if not isinstance(a, _np.ndarray):
634
+ a = _np.asarray(a)
635
+ if n is None:
636
+ n = 2 * (a.shape[axis] - 1)
637
+ cost = _batch_count_1d(a, axis) * hfft_cost(n)
638
+ with budget.deduct("fft.hfft", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
639
+ result = _call_numpy(
640
+ _np.fft.hfft,
641
+ _to_base_ndarray(a),
642
+ n=n,
643
+ axis=axis,
644
+ norm=norm, # type: ignore[reportArgumentType]
645
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
646
+ )
647
+ return out if out is not None else result # type: ignore[reportReturnType]
648
+
649
+
650
+ attach_docstring(
651
+ hfft,
652
+ _np.fft.hfft,
653
+ "fft",
654
+ "5 \u00d7 n_out \u00d7 \u2308log\u2082(n_out)\u2309 FLOPs",
655
+ )
656
+
657
+
658
+ @_counted_wrapper
659
+ def ihfft(
660
+ a: ArrayLike,
661
+ n: int | None = None,
662
+ axis: int = -1,
663
+ norm: str | None = None,
664
+ out: ArrayLike | None = None,
665
+ ) -> FlopscopeArray:
666
+ budget = require_budget()
667
+ if not isinstance(a, _np.ndarray):
668
+ a = _np.asarray(a)
669
+ if n is None:
670
+ n = a.shape[axis]
671
+ cost = _batch_count_1d(a, axis) * hfft_cost(n)
672
+ with budget.deduct("fft.ihfft", flop_cost=cost, subscripts=None, shapes=(a.shape,)):
673
+ result = _call_numpy(
674
+ _np.fft.ihfft,
675
+ _to_base_ndarray(a),
676
+ n=n,
677
+ axis=axis,
678
+ norm=norm, # type: ignore[reportArgumentType]
679
+ out=_to_base_ndarray(out) if out is not None else None, # type: ignore[reportArgumentType]
680
+ )
681
+ return out if out is not None else result # type: ignore[reportReturnType]
682
+
683
+
684
+ attach_docstring(
685
+ ihfft,
686
+ _np.fft.ihfft,
687
+ "fft",
688
+ "5 \u00d7 n_out \u00d7 \u2308log\u2082(n_out)\u2309 FLOPs",
689
+ )
690
+
691
+ import sys as _sys # noqa: E402
692
+
693
+ from flopscope._ndarray import wrap_module_returns as _wrap_module_returns # noqa: E402
694
+
695
+ _wrap_module_returns(_sys.modules[__name__])