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
|
@@ -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
|
+
)
|