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,164 @@
1
+ """Contains helper functions for opt_einsum testing scripts."""
2
+
3
+ from collections.abc import Collection, Iterable, Sequence
4
+ from typing import Any, overload
5
+
6
+ # Inline type aliases (formerly from ._typing, deleted in Task 7+8).
7
+ ArrayIndexType = frozenset # frozenset[str]
8
+ ArrayType = object # Any
9
+
10
+ __all__ = ["compute_size_by_dict", "find_contraction", "flop_count"]
11
+
12
+ _valid_chars = "abcdefghijklmopqABC"
13
+ _sizes = [2, 3, 4, 5, 4, 3, 2, 6, 5, 4, 3, 2, 5, 7, 4, 3, 2, 3, 4]
14
+ _default_dim_dict = dict(zip(_valid_chars, _sizes, strict=False))
15
+
16
+
17
+ @overload
18
+ def compute_size_by_dict(indices: Iterable[int], idx_dict: list[int]) -> int: ...
19
+
20
+
21
+ @overload
22
+ def compute_size_by_dict(indices: Collection[str], idx_dict: dict[str, int]) -> int: ...
23
+
24
+
25
+ def compute_size_by_dict(indices: Any, idx_dict: Any) -> int:
26
+ """Computes the product of the elements in indices based on the dictionary
27
+ idx_dict.
28
+
29
+ Parameters
30
+ ----------
31
+ indices : iterable
32
+ Indices to base the product on.
33
+ idx_dict : dictionary
34
+ Dictionary of index _sizes
35
+
36
+ Returns:
37
+ -------
38
+ ret : int
39
+ The resulting product.
40
+
41
+ Examples:
42
+ --------
43
+ >>> compute_size_by_dict('abbc', {'a': 2, 'b':3, 'c':5})
44
+ 90
45
+
46
+ """
47
+ ret = 1
48
+ for i in indices: # lgtm [py/iteration-string-and-sequence]
49
+ ret *= idx_dict[i]
50
+ return ret
51
+
52
+
53
+ def find_contraction(
54
+ positions: Collection[int],
55
+ input_sets: list[ArrayIndexType],
56
+ output_set: ArrayIndexType,
57
+ ) -> tuple[frozenset[str], list[ArrayIndexType], ArrayIndexType, ArrayIndexType]:
58
+ """Finds the contraction for a given set of input and output sets.
59
+
60
+ Parameters
61
+ ----------
62
+ positions : iterable
63
+ Integer positions of terms used in the contraction.
64
+ input_sets : list
65
+ List of sets that represent the lhs side of the einsum subscript
66
+ output_set : set
67
+ Set that represents the rhs side of the overall einsum subscript
68
+
69
+ Returns:
70
+ -------
71
+ new_result : set
72
+ The indices of the resulting contraction
73
+ remaining : list
74
+ List of sets that have not been contracted, the new set is appended to
75
+ the end of this list
76
+ idx_removed : set
77
+ Indices removed from the entire contraction
78
+ idx_contraction : set
79
+ The indices used in the current contraction
80
+
81
+ Examples:
82
+ --------
83
+ # A simple dot product test case
84
+ >>> pos = (0, 1)
85
+ >>> isets = [set('ab'), set('bc')]
86
+ >>> oset = set('ac')
87
+ >>> find_contraction(pos, isets, oset)
88
+ ({'a', 'c'}, [{'a', 'c'}], {'b'}, {'a', 'b', 'c'})
89
+
90
+ # A more complex case with additional terms in the contraction
91
+ >>> pos = (0, 2)
92
+ >>> isets = [set('abd'), set('ac'), set('bdc')]
93
+ >>> oset = set('ac')
94
+ >>> find_contraction(pos, isets, oset)
95
+ ({'a', 'c'}, [{'a', 'c'}, {'a', 'c'}], {'b', 'd'}, {'a', 'b', 'c', 'd'})
96
+ """
97
+ remaining = list(input_sets)
98
+ inputs = (remaining.pop(i) for i in sorted(positions, reverse=True))
99
+ idx_contract = frozenset.union(*inputs)
100
+ idx_remain = output_set.union(*remaining)
101
+
102
+ new_result = idx_remain & idx_contract
103
+ idx_removed = idx_contract - new_result
104
+ remaining.append(new_result)
105
+
106
+ return new_result, remaining, idx_removed, idx_contract
107
+
108
+
109
+ def flop_count(
110
+ idx_contraction: Collection[str],
111
+ inner: bool,
112
+ num_terms: int,
113
+ size_dictionary: dict[str, int],
114
+ *,
115
+ input_subscripts: Sequence[str] | None = None,
116
+ output_subscript: str | None = None,
117
+ input_shapes: Sequence[Sequence[int]] | None = None,
118
+ ) -> int:
119
+ """Per-step dense FLOP count (no declared symmetry).
120
+
121
+ Routes through compute_accumulation_cost so the result is consistent
122
+ with what ``einsum_accumulation_cost`` would return for an einsum with
123
+ no declared input symmetry. The α/M formula produces textbook FMA=2
124
+ scalar-op counts (multiplies and adds counted separately, with the
125
+ off-by-one accumulator-init credit applied).
126
+
127
+ Legacy fallback (when subscripts/shapes are not provided): the old
128
+ overall_size * op_factor formula with FMA=1 (inner does not add an extra
129
+ op).
130
+
131
+ Examples:
132
+ --------
133
+ >>> flop_count('abc', False, 1, {'a': 2, 'b':3, 'c':5})
134
+ 30
135
+
136
+ >>> flop_count('abc', True, 2, {'a': 2, 'b':3, 'c':5})
137
+ 30
138
+
139
+ """
140
+ if input_subscripts is None or input_shapes is None:
141
+ overall_size = compute_size_by_dict(idx_contraction, size_dictionary)
142
+ op_factor = max(1, num_terms - 1)
143
+ return overall_size * op_factor
144
+
145
+ from flopscope._accumulation._cost import compute_accumulation_cost
146
+
147
+ canonical = ",".join(input_subscripts) + "->" + (output_subscript or "")
148
+ per_op_syms = (None,) * len(input_subscripts)
149
+ cost = compute_accumulation_cost(
150
+ canonical_subscripts=canonical,
151
+ input_parts=tuple(input_subscripts),
152
+ output_subscript=output_subscript or "",
153
+ shapes=tuple(tuple(s) for s in input_shapes),
154
+ per_op_symmetries=per_op_syms,
155
+ identity_pattern=None,
156
+ )
157
+ return max(cost.total, 1)
158
+
159
+
160
+ def has_array_interface(array: ArrayType) -> ArrayType:
161
+ if hasattr(array, "__array_interface__"):
162
+ return True
163
+ else:
164
+ return False
@@ -0,0 +1,273 @@
1
+ """Minimal HSLuv helpers for terminal label palettes.
2
+
3
+ Adapted from the MIT-licensed ``hsluv-python`` reference implementation:
4
+ https://github.com/hsluv/hsluv-python
5
+
6
+ The palette construction idea mirrors seaborn's ``husl_palette`` API, but
7
+ this module does not vendor seaborn code or add a seaborn dependency.
8
+ See NOTICE in this package for attribution details.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import math
14
+ from functools import cache
15
+
16
+ _M = (
17
+ (3.240969941904521, -1.537383177570093, -0.498610760293),
18
+ (-0.96924363628087, 1.87596750150772, 0.041555057407175),
19
+ (0.055630079696993, -0.20397695888897, 1.056971514242878),
20
+ )
21
+ _M_INV = (
22
+ (0.41239079926595, 0.35758433938387, 0.18048078840183),
23
+ (0.21263900587151, 0.71516867876775, 0.072192315360733),
24
+ (0.019330818715591, 0.11919477979462, 0.95053215224966),
25
+ )
26
+ _REF_Y = 1.0
27
+ _REF_U = 0.19783000664283
28
+ _REF_V = 0.46831999493879
29
+ _KAPPA = 903.2962962
30
+ _EPSILON = 0.0088564516
31
+
32
+ # Frozen to preserve the current Rich label colors. Keep this in sync with
33
+ # qualitative_hsluv_palette(64, lightness=0.5, saturation=0.9).
34
+ PRECOMPUTED_QUALITATIVE_HSLUV_PALETTE_64 = (
35
+ "#E3245A",
36
+ "#2F70EF",
37
+ "#228921",
38
+ "#B92FED",
39
+ "#987022",
40
+ "#268389",
41
+ "#7B5EF0",
42
+ "#D529A6",
43
+ "#D14923",
44
+ "#5E8222",
45
+ "#238755",
46
+ "#2A7CBC",
47
+ "#E7232C",
48
+ "#9D4AF0",
49
+ "#5569F0",
50
+ "#DD2680",
51
+ "#C82CCA",
52
+ "#B75F22",
53
+ "#7C7B22",
54
+ "#408722",
55
+ "#24856F",
56
+ "#22893B",
57
+ "#2C76D5",
58
+ "#2880A3",
59
+ "#E62443",
60
+ "#DF3623",
61
+ "#6864F0",
62
+ "#D92893",
63
+ "#8D55F0",
64
+ "#AC3DF0",
65
+ "#426DF0",
66
+ "#E1256D",
67
+ "#CF2AB8",
68
+ "#C12EDC",
69
+ "#A86822",
70
+ "#C55522",
71
+ "#6D7E22",
72
+ "#4F8422",
73
+ "#318822",
74
+ "#8A7622",
75
+ "#2E73E2",
76
+ "#25847C",
77
+ "#278196",
78
+ "#228848",
79
+ "#238662",
80
+ "#22892E",
81
+ "#2B79C8",
82
+ "#297EAF",
83
+ "#E42B23",
84
+ "#D84023",
85
+ "#E5244F",
86
+ "#E72337",
87
+ "#A544F0",
88
+ "#845AF0",
89
+ "#C52DD3",
90
+ "#CC2BC1",
91
+ "#DC278A",
92
+ "#5F67F0",
93
+ "#7161F0",
94
+ "#D22AAF",
95
+ "#9550F0",
96
+ "#D7289D",
97
+ "#DF2676",
98
+ "#4B6BF0",
99
+ )
100
+
101
+
102
+ def _distance_line_from_origin(line: tuple[float, float]) -> float:
103
+ slope, intercept = line
104
+ return abs(intercept) / math.sqrt(slope**2 + 1)
105
+
106
+
107
+ def _length_of_ray_until_intersect(theta: float, line: tuple[float, float]) -> float:
108
+ slope, intercept = line
109
+ return intercept / (math.sin(theta) - slope * math.cos(theta))
110
+
111
+
112
+ def _get_bounds(lightness: float) -> list[tuple[float, float]]:
113
+ result = []
114
+ sub1 = ((lightness + 16) ** 3) / 1560896
115
+ sub2 = sub1 if sub1 > _EPSILON else lightness / _KAPPA
116
+
117
+ for channel in range(3):
118
+ m1, m2, m3 = _M[channel]
119
+ for t in range(2):
120
+ top1 = (284517 * m1 - 94839 * m3) * sub2
121
+ top2 = (
122
+ 838422 * m3 + 769860 * m2 + 731718 * m1
123
+ ) * lightness * sub2 - 769860 * t * lightness
124
+ bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t
125
+ result.append((top1 / bottom, top2 / bottom))
126
+
127
+ return result
128
+
129
+
130
+ def _max_chroma_for_lh(lightness: float, hue: float) -> float:
131
+ hue_radians = math.radians(hue)
132
+ lengths = [
133
+ _length_of_ray_until_intersect(hue_radians, bound)
134
+ for bound in _get_bounds(lightness)
135
+ ]
136
+ return min(length for length in lengths if length >= 0)
137
+
138
+
139
+ def _dot_product(
140
+ left: tuple[float, float, float], right: tuple[float, float, float]
141
+ ) -> float:
142
+ return sum(
143
+ left_value * right_value
144
+ for left_value, right_value in zip(left, right, strict=False)
145
+ )
146
+
147
+
148
+ def _from_linear(channel: float) -> float:
149
+ if channel <= 0.0031308:
150
+ return 12.92 * channel
151
+ return 1.055 * math.pow(channel, 5 / 12) - 0.055
152
+
153
+
154
+ def _l_to_y(lightness: float) -> float:
155
+ if lightness <= 8:
156
+ return _REF_Y * lightness / _KAPPA
157
+ return _REF_Y * (((lightness + 16) / 116) ** 3)
158
+
159
+
160
+ def _lch_to_luv(color: tuple[float, float, float]) -> tuple[float, float, float]:
161
+ lightness, chroma, hue = color
162
+ hue_radians = math.radians(hue)
163
+ return (
164
+ lightness,
165
+ math.cos(hue_radians) * chroma,
166
+ math.sin(hue_radians) * chroma,
167
+ )
168
+
169
+
170
+ def _luv_to_xyz(color: tuple[float, float, float]) -> tuple[float, float, float]:
171
+ lightness, u_value, v_value = color
172
+ if lightness == 0:
173
+ return (0.0, 0.0, 0.0)
174
+
175
+ var_u = u_value / (13 * lightness) + _REF_U
176
+ var_v = v_value / (13 * lightness) + _REF_V
177
+ y_value = _l_to_y(lightness)
178
+ x_value = y_value * 9 * var_u / (4 * var_v)
179
+ z_value = y_value * (12 - 3 * var_u - 20 * var_v) / (4 * var_v)
180
+ return (x_value, y_value, z_value)
181
+
182
+
183
+ def _xyz_to_rgb(color: tuple[float, float, float]) -> tuple[float, float, float]:
184
+ return tuple(_from_linear(_dot_product(row, color)) for row in _M) # type: ignore[return-value]
185
+
186
+
187
+ def _hsluv_to_lch(color: tuple[float, float, float]) -> tuple[float, float, float]:
188
+ hue, saturation, lightness = color
189
+ if lightness > 100 - 1e-7:
190
+ return (100.0, 0.0, hue)
191
+ if lightness < 1e-8:
192
+ return (0.0, 0.0, hue)
193
+ max_chroma = _max_chroma_for_lh(lightness, hue)
194
+ return (lightness, max_chroma * saturation / 100, hue)
195
+
196
+
197
+ def hsluv_to_rgb(color: tuple[float, float, float]) -> tuple[float, float, float]:
198
+ return _xyz_to_rgb(_luv_to_xyz(_lch_to_luv(_hsluv_to_lch(color))))
199
+
200
+
201
+ def rgb_to_hex(color: tuple[float, float, float]) -> str:
202
+ channels = []
203
+ for channel in color:
204
+ channel = min(max(channel, 0.0), 1.0)
205
+ channels.append(int(math.floor(channel * 255 + 0.5)))
206
+ return "#{:02X}{:02X}{:02X}".format(*channels)
207
+
208
+
209
+ def hsluv_to_hex(color: tuple[float, float, float]) -> str:
210
+ return rgb_to_hex(hsluv_to_rgb(color))
211
+
212
+
213
+ def _hex_to_rgb(color: str) -> tuple[int, int, int]:
214
+ return tuple(int(color[index : index + 2], 16) for index in (1, 3, 5)) # type: ignore[return-value]
215
+
216
+
217
+ def rgb_distance_hex(left: str, right: str) -> float:
218
+ left_rgb = _hex_to_rgb(left)
219
+ right_rgb = _hex_to_rgb(right)
220
+ return math.dist(left_rgb, right_rgb)
221
+
222
+
223
+ @cache
224
+ def rich_label_palette(slot_count: int) -> tuple[str, ...]:
225
+ if slot_count <= 64:
226
+ return PRECOMPUTED_QUALITATIVE_HSLUV_PALETTE_64
227
+ return tuple(qualitative_hsluv_palette(slot_count, lightness=0.5, saturation=0.9))
228
+
229
+
230
+ def qualitative_hsluv_palette(
231
+ n_colors: int,
232
+ *,
233
+ hue: float = 0.01,
234
+ saturation: float = 0.9,
235
+ lightness: float = 0.5,
236
+ hue_samples: int | None = None,
237
+ ) -> list[str]:
238
+ """Build a deterministic, high-separation qualitative HSLuv palette.
239
+
240
+ Parameters mirror seaborn's ``husl_palette`` convention, using normalized
241
+ floats in ``[0, 1]`` for hue, saturation, and lightness.
242
+ """
243
+ if n_colors <= 0:
244
+ return []
245
+
246
+ if hue_samples is None:
247
+ hue_samples = max(48, n_colors * 32)
248
+
249
+ start_degrees = (hue % 1.0) * 360.0
250
+ candidates = [
251
+ hsluv_to_hex(
252
+ (
253
+ (start_degrees + 360.0 * index / hue_samples) % 360.0,
254
+ saturation * 100.0,
255
+ lightness * 100.0,
256
+ )
257
+ )
258
+ for index in range(hue_samples)
259
+ ]
260
+ candidates = list(dict.fromkeys(candidates))
261
+
262
+ selected = [candidates[0]]
263
+ while len(selected) < min(n_colors, len(candidates)):
264
+ remaining = [candidate for candidate in candidates if candidate not in selected]
265
+ best = max(
266
+ remaining,
267
+ key=lambda candidate: min(
268
+ rgb_distance_hex(candidate, chosen) for chosen in selected
269
+ ),
270
+ )
271
+ selected.append(best)
272
+
273
+ return selected[:n_colors]