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,105 @@
1
+ """Linear algebra submodule for flopscope."""
2
+
3
+ from flopscope._registry import make_module_getattr as _make_module_getattr
4
+ from flopscope.numpy.linalg._aliases import ( # noqa: F401
5
+ cross,
6
+ diagonal,
7
+ matmul,
8
+ matrix_transpose,
9
+ outer,
10
+ tensordot,
11
+ vecdot,
12
+ )
13
+ from flopscope.numpy.linalg._compound import ( # noqa: F401
14
+ matrix_power,
15
+ matrix_power_cost,
16
+ multi_dot,
17
+ multi_dot_cost,
18
+ )
19
+ from flopscope.numpy.linalg._decompositions import ( # noqa: F401
20
+ cholesky,
21
+ cholesky_cost,
22
+ eig,
23
+ eig_cost,
24
+ eigh,
25
+ eigh_cost,
26
+ eigvals,
27
+ eigvals_cost,
28
+ eigvalsh,
29
+ eigvalsh_cost,
30
+ qr,
31
+ qr_cost,
32
+ svdvals,
33
+ svdvals_cost,
34
+ )
35
+ from flopscope.numpy.linalg._properties import ( # noqa: F401
36
+ cond,
37
+ cond_cost,
38
+ det,
39
+ det_cost,
40
+ matrix_norm,
41
+ matrix_norm_cost,
42
+ matrix_rank,
43
+ matrix_rank_cost,
44
+ norm,
45
+ norm_cost,
46
+ slogdet,
47
+ slogdet_cost,
48
+ trace,
49
+ trace_cost,
50
+ vector_norm,
51
+ vector_norm_cost,
52
+ )
53
+ from flopscope.numpy.linalg._solvers import ( # noqa: F401
54
+ inv,
55
+ inv_cost,
56
+ lstsq,
57
+ lstsq_cost,
58
+ pinv,
59
+ pinv_cost,
60
+ solve,
61
+ solve_cost,
62
+ tensorinv,
63
+ tensorinv_cost,
64
+ tensorsolve,
65
+ tensorsolve_cost,
66
+ )
67
+ from flopscope.numpy.linalg._svd import svd # noqa: F401
68
+
69
+ __all__ = [
70
+ "svd",
71
+ "matmul",
72
+ "cross",
73
+ "outer",
74
+ "tensordot",
75
+ "vecdot",
76
+ "diagonal",
77
+ "matrix_transpose",
78
+ "cholesky",
79
+ "qr",
80
+ "eig",
81
+ "eigh",
82
+ "eigvals",
83
+ "eigvalsh",
84
+ "svdvals",
85
+ "solve",
86
+ "inv",
87
+ "lstsq",
88
+ "pinv",
89
+ "tensorsolve",
90
+ "tensorinv",
91
+ "trace",
92
+ "det",
93
+ "slogdet",
94
+ "norm",
95
+ "vector_norm",
96
+ "matrix_norm",
97
+ "cond",
98
+ "matrix_rank",
99
+ "multi_dot",
100
+ "matrix_power",
101
+ ]
102
+
103
+ __getattr__ = _make_module_getattr(
104
+ module_prefix="linalg.", module_label="flopscope.numpy.linalg"
105
+ )
@@ -0,0 +1,126 @@
1
+ # src/flopscope/linalg/_aliases.py
2
+ """Linalg namespace aliases that delegate to top-level flopscope operations.
3
+
4
+ These functions exist in numpy.linalg as convenience aliases.
5
+ FLOP costs are handled by the delegated top-level implementations.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import numpy as _np
13
+ from numpy.typing import ArrayLike
14
+
15
+ import flopscope.numpy as _me
16
+ from flopscope._budget import _call_numpy, _counted_wrapper
17
+ from flopscope._docstrings import attach_docstring
18
+ from flopscope._ndarray import FlopscopeArray
19
+
20
+
21
+ def matmul(x1: ArrayLike, x2: ArrayLike, /) -> FlopscopeArray:
22
+ """Matrix multiply (linalg namespace). Delegates to flopscope.matmul."""
23
+ return _me.matmul(x1, x2) # type: ignore[reportReturnType]
24
+
25
+
26
+ attach_docstring(
27
+ matmul, _np.linalg.matmul, "linalg", "0 FLOPs (delegates to flopscope.matmul)"
28
+ )
29
+
30
+
31
+ @_counted_wrapper
32
+ def cross(x1: ArrayLike, x2: ArrayLike, /, *, axis: int = -1) -> FlopscopeArray:
33
+ """Cross product (linalg namespace). Uses np.linalg.cross for strict validation."""
34
+ import builtins as _builtins
35
+
36
+ from flopscope._ndarray import FlopscopeArray, _asflopscope, _to_base_ndarray
37
+ from flopscope._validation import require_budget
38
+
39
+ budget = require_budget()
40
+ inputs_were_whest = isinstance(x1, FlopscopeArray) or isinstance(x2, FlopscopeArray)
41
+ x1_arr = _np.asarray(x1)
42
+ x2_arr = _np.asarray(x2)
43
+ out_shape = _np.broadcast_shapes(x1_arr.shape, x2_arr.shape)
44
+ out_size = 1
45
+ for d in out_shape:
46
+ out_size *= d
47
+ with budget.deduct(
48
+ "linalg.cross",
49
+ flop_cost=_builtins.max(out_size * 5, 1),
50
+ subscripts=None,
51
+ shapes=(x1_arr.shape, x2_arr.shape),
52
+ ):
53
+ result = _call_numpy(
54
+ _np.linalg.cross, _to_base_ndarray(x1), _to_base_ndarray(x2), axis=axis
55
+ ) # type: ignore[reportCallIssue]
56
+ if isinstance(result, _np.ndarray) and inputs_were_whest:
57
+ return _asflopscope(result) # type: ignore[reportReturnType]
58
+ return result # type: ignore[reportReturnType]
59
+
60
+
61
+ attach_docstring(
62
+ cross, _np.linalg.cross, "linalg", "0 FLOPs (delegates to flopscope.cross)"
63
+ )
64
+
65
+
66
+ def outer(x1: ArrayLike, x2: ArrayLike, /) -> FlopscopeArray:
67
+ """Outer product (linalg namespace). Delegates to flopscope.outer."""
68
+ return _me.outer(x1, x2) # type: ignore[reportReturnType]
69
+
70
+
71
+ attach_docstring(
72
+ outer, _np.linalg.outer, "linalg", "0 FLOPs (delegates to flopscope.outer)"
73
+ )
74
+
75
+
76
+ def tensordot(x1: ArrayLike, x2: ArrayLike, /, *, axes: Any = 2) -> FlopscopeArray:
77
+ """Tensor dot product (linalg namespace). Delegates to flopscope.tensordot."""
78
+ return _me.tensordot(x1, x2, axes=axes) # type: ignore[reportReturnType]
79
+
80
+
81
+ attach_docstring(
82
+ tensordot,
83
+ _np.linalg.tensordot,
84
+ "linalg",
85
+ "0 FLOPs (delegates to flopscope.tensordot)",
86
+ )
87
+
88
+
89
+ if hasattr(_np.linalg, "vecdot"):
90
+
91
+ def vecdot( # type: ignore[reportRedeclaration]
92
+ x1: ArrayLike, x2: ArrayLike, /, *, axis: int = -1
93
+ ) -> FlopscopeArray:
94
+ """Vector dot product (linalg namespace). Delegates to flopscope.vecdot."""
95
+ return _me.vecdot(x1, x2, axis=axis) # type: ignore[reportReturnType]
96
+
97
+ attach_docstring(
98
+ vecdot,
99
+ _np.linalg.vecdot,
100
+ "linalg",
101
+ "0 FLOPs (delegates to flopscope.vecdot)",
102
+ )
103
+
104
+ else:
105
+ from flopscope.errors import UnsupportedFunctionError
106
+
107
+ def vecdot(*args: Any, **kwargs: Any) -> FlopscopeArray:
108
+ raise UnsupportedFunctionError("linalg.vecdot", min_version="2.1")
109
+
110
+
111
+ def diagonal(x: ArrayLike, /, *, offset: int = 0) -> FlopscopeArray:
112
+ """Diagonal (linalg namespace). Delegates to flopscope.diagonal. Cost: 0 FLOPs."""
113
+ return _me.diagonal(x, offset=offset, axis1=-2, axis2=-1) # type: ignore[reportReturnType]
114
+
115
+
116
+ attach_docstring(diagonal, _np.linalg.diagonal, "linalg", "0 FLOPs (free)")
117
+
118
+
119
+ def matrix_transpose(x: ArrayLike, /) -> FlopscopeArray:
120
+ """Transpose (linalg namespace). Delegates to flopscope.matrix_transpose. Cost: 0 FLOPs."""
121
+ return _me.matrix_transpose(x) # type: ignore[reportReturnType]
122
+
123
+
124
+ attach_docstring(
125
+ matrix_transpose, _np.linalg.matrix_transpose, "linalg", "0 FLOPs (free)"
126
+ )
@@ -0,0 +1,161 @@
1
+ """Compound linalg operations with FLOP counting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Sequence
7
+
8
+ import numpy as _np
9
+ from numpy.typing import ArrayLike
10
+
11
+ from flopscope._budget import _call_numpy, _counted_wrapper
12
+ from flopscope._docstrings import attach_docstring
13
+ from flopscope._ndarray import FlopscopeArray, _asflopscope, _to_base_ndarray
14
+ from flopscope._validation import require_budget
15
+ from flopscope.numpy.linalg._solvers import _batch_size, _has_zero_dim
16
+
17
+
18
+ def _popcount(n: int) -> int:
19
+ count = 0
20
+ while n:
21
+ count += n & 1
22
+ n >>= 1
23
+ return count
24
+
25
+
26
+ def multi_dot_cost(shapes: Sequence[Sequence[int]]) -> int:
27
+ """FLOP cost of optimal matrix chain multiplication.
28
+
29
+ Parameters
30
+ ----------
31
+ shapes : list of tuple of int
32
+ Shapes of the matrices to be multiplied.
33
+
34
+ Returns
35
+ -------
36
+ int
37
+ Estimated FLOP count using optimal parenthesization.
38
+
39
+ Notes
40
+ -----
41
+ Uses dynamic programming for optimal parenthesization.
42
+ Source: Cormen et al., *Introduction to Algorithms* (CLRS), §15.2.
43
+
44
+ Each binary matmul step (m x k) @ (k x n) costs ``2 * m * k * n`` FLOPs
45
+ under the FMA=2 textbook convention (m*k*n multiplies + m*k*n adds,
46
+ ignoring the −m*n off-by-one for accumulation at this level of
47
+ approximation). FMA=2 unification 2026-05-20.
48
+ """
49
+ n = len(shapes)
50
+ if n < 2:
51
+ return 0
52
+ dims = [s[0] for s in shapes] + [shapes[-1][-1]]
53
+ if n == 2:
54
+ return 2 * dims[0] * dims[1] * dims[2]
55
+ cost_table = [[0] * n for _ in range(n)]
56
+ for chain_len in range(2, n + 1):
57
+ for i in range(n - chain_len + 1):
58
+ j = i + chain_len - 1
59
+ cost_table[i][j] = float("inf") # type: ignore[reportAssignmentType]
60
+ for k in range(i, j):
61
+ cost = (
62
+ cost_table[i][k]
63
+ + cost_table[k + 1][j]
64
+ + 2 * dims[i] * dims[k + 1] * dims[j + 1]
65
+ )
66
+ if cost < cost_table[i][j]:
67
+ cost_table[i][j] = cost
68
+ return max(int(cost_table[0][n - 1]), 1)
69
+
70
+
71
+ @_counted_wrapper
72
+ def multi_dot(
73
+ arrays: Sequence[ArrayLike], *, out: ArrayLike | None = None
74
+ ) -> FlopscopeArray:
75
+ """Efficient multi-matrix dot product with FLOP counting."""
76
+ budget = require_budget()
77
+ inputs_were_whest = any(isinstance(a, FlopscopeArray) for a in arrays)
78
+ arrays = [a if isinstance(a, _np.ndarray) else _np.asarray(a) for a in arrays]
79
+ shapes = [arr.shape for arr in arrays]
80
+ cost = multi_dot_cost(shapes)
81
+ out_stripped = _to_base_ndarray(out) if out is not None else None
82
+ with budget.deduct(
83
+ "linalg.multi_dot", flop_cost=cost, subscripts=None, shapes=tuple(shapes)
84
+ ):
85
+ result = _call_numpy(
86
+ _np.linalg.multi_dot,
87
+ [_to_base_ndarray(a) for a in arrays],
88
+ out=out_stripped, # type: ignore[reportArgumentType]
89
+ )
90
+ if out is not None:
91
+ return out # type: ignore[reportReturnType]
92
+ if isinstance(result, _np.ndarray) and inputs_were_whest:
93
+ return _asflopscope(result) # type: ignore[reportReturnType]
94
+ return result # type: ignore[reportReturnType]
95
+
96
+
97
+ attach_docstring(
98
+ multi_dot, _np.linalg.multi_dot, "linalg", "Optimal chain multiplication cost"
99
+ )
100
+
101
+
102
+ def matrix_power_cost(n: int, k: int) -> int:
103
+ """FLOP cost of matrix power ``A**k``.
104
+
105
+ Parameters
106
+ ----------
107
+ n : int
108
+ Matrix dimension.
109
+ k : int
110
+ Exponent.
111
+
112
+ Returns
113
+ -------
114
+ int
115
+ Estimated FLOP count.
116
+
117
+ Notes
118
+ -----
119
+ Uses exponentiation by repeated squaring. For ``k < 0``, adds
120
+ ``matmul_cost(n, n, n)`` for the initial matrix inversion (LU-based).
121
+ Per-matmul cost is delegated to ``matmul_cost(n, n, n)`` so this formula
122
+ tracks ``fnp.matmul``'s convention automatically (issue #69; the FMA=2
123
+ unification of 2026-05-20 previously left this wrapper undercounting
124
+ by ~2x).
125
+ """
126
+ from flopscope._flops import matmul_cost
127
+
128
+ if k == 0 or k == 1:
129
+ return 0
130
+ if k < 0:
131
+ # Inversion: LU-based, ~2*n^3 - n^2, modelled as one matmul-equivalent.
132
+ return matmul_cost(n, n, n) + matrix_power_cost(n, abs(k))
133
+ num_ops = math.floor(math.log2(k)) + _popcount(k) - 1
134
+ return max(num_ops * matmul_cost(n, n, n), 1)
135
+
136
+
137
+ @_counted_wrapper
138
+ def matrix_power(a: ArrayLike, n: int) -> FlopscopeArray:
139
+ """Matrix power with FLOP counting."""
140
+ budget = require_budget()
141
+ inputs_were_whest = isinstance(a, FlopscopeArray)
142
+ if not isinstance(a, _np.ndarray):
143
+ a = _np.asarray(a)
144
+ size = a.shape[-1]
145
+ batch = _batch_size(a.shape)
146
+ cost = matrix_power_cost(size, n) * batch if not _has_zero_dim(a.shape) else 0
147
+ with budget.deduct(
148
+ "linalg.matrix_power", flop_cost=cost, subscripts=None, shapes=(a.shape,)
149
+ ):
150
+ result = _call_numpy(_np.linalg.matrix_power, _to_base_ndarray(a), n)
151
+ if isinstance(result, _np.ndarray) and inputs_were_whest:
152
+ return _asflopscope(result) # type: ignore[reportReturnType]
153
+ return result # type: ignore[reportReturnType]
154
+
155
+
156
+ attach_docstring(
157
+ matrix_power,
158
+ _np.linalg.matrix_power,
159
+ "linalg",
160
+ r"$n^3 \times \text{exponent}$ FLOPs (repeated squaring)",
161
+ )