counted-float 1.0.4__tar.gz → 1.1.0__tar.gz

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 (96) hide show
  1. {counted_float-1.0.4 → counted_float-1.1.0}/PKG-INFO +36 -22
  2. {counted_float-1.0.4 → counted_float-1.1.0}/README.md +35 -21
  3. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/counted_float/_counted_float_benchmark.py +4 -0
  4. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/counting/_builtin_data.py +5 -8
  5. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/counting/_context_managers.py +5 -4
  6. counted_float-1.1.0/counted_float/_core/counting/_counted_float.py +205 -0
  7. counted_float-1.1.0/counted_float/_core/counting/_math_patching.py +270 -0
  8. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/counting/config/_config.py +2 -1
  9. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/counting/config/_defaults.py +10 -2
  10. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_flop_weights.py +2 -2
  11. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/_geo_mean.py +1 -1
  12. {counted_float-1.0.4 → counted_float-1.1.0}/pyproject.toml +1 -1
  13. counted_float-1.0.4/counted_float/_core/counting/_counted_float.py +0 -402
  14. {counted_float-1.0.4 → counted_float-1.1.0}/.gitignore +0 -0
  15. {counted_float-1.0.4 → counted_float-1.1.0}/LICENSE +0 -0
  16. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/__init__.py +0 -0
  17. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/__init__.py +0 -0
  18. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/_cli.py +0 -0
  19. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/__init__.py +0 -0
  20. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/counted_float/__init__.py +0 -0
  21. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/__init__.py +0 -0
  22. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/_array_generator.py +0 -0
  23. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/_flops_benchmark_suite.py +0 -0
  24. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/_flops_micro_benchmark.py +0 -0
  25. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/micro/__init__.py +0 -0
  26. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/benchmarking/micro/_micro_benchmark.py +0 -0
  27. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/compatibility/__init__.py +0 -0
  28. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/compatibility/_numba.py +0 -0
  29. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/counting/__init__.py +0 -0
  30. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/counting/_global_counter.py +0 -0
  31. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/counting/config/__init__.py +0 -0
  32. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/__init__.py +0 -0
  33. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_base.py +0 -0
  34. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_flop_counts.py +0 -0
  35. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_flop_type.py +0 -0
  36. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_flops_benchmark_meta_data.py +0 -0
  37. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_flops_benchmark_result.py +0 -0
  38. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_flops_benchmark_type.py +0 -0
  39. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_instruction_latencies.py +0 -0
  40. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/models/_micro_benchmark_result.py +0 -0
  41. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/__init__.py +0 -0
  42. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/_cpu_freq.py +0 -0
  43. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/_formatting.py +0 -0
  44. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/_latency.py +0 -0
  45. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/_missing_data.py +0 -0
  46. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/_rounding.py +0 -0
  47. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/_core/utils/_timer.py +0 -0
  48. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/benchmarking/__init__.py +0 -0
  49. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/config/__init__.py +0 -0
  50. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/apple_m1_github_actions.json +0 -0
  51. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/apple_m3_max_mbp16.json +0 -0
  52. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/apple_m3_mba15.json +0 -0
  53. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/aws_graviton_2_neoverse_n1_ec2_m6g_xlarge.json +0 -0
  54. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/aws_graviton_3_neoverse_v1_ec2_m7g_xlarge.json +0 -0
  55. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_cortex_a76.json +0 -0
  56. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_cortex_x1.json +0 -0
  57. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_neoverse_n1.json +0 -0
  58. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_neoverse_v1.json +0 -0
  59. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_0/benchmarks/aws_graviton_4_neoverse_v2_ec2_m8g_xlarge.json +0 -0
  60. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_0/benchmarks/azure_cobalt_100_neoverse_n2_github_actions.json +0 -0
  61. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_cortex_x2.json +0 -0
  62. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_cortex_x3.json +0 -0
  63. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_neoverse_n2.json +0 -0
  64. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_neoverse_v2.json +0 -0
  65. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_2/benchmarks/apple_m4_pro_mbp16.json +0 -0
  66. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_2/specs/arm_cortex_x4.json +0 -0
  67. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_2/specs/arm_cortex_x925.json +0 -0
  68. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/arm/v9_2/specs/arm_neoverse_v3.json +0 -0
  69. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2017_zen1/benchmarks/amd_ryzen_1700x.json +0 -0
  70. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2017_zen1/other/analysis_uops_info_zen1+.json +0 -0
  71. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/benchmark/amd_epyc_7763_linux_github.json +0 -0
  72. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/benchmark/amd_epyc_7763_windows_github.json +0 -0
  73. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/other/analysis_agner_fog_r7_5800x.json +0 -0
  74. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/other/analysis_uops_info_zen3.json +0 -0
  75. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/benchmark/amd_epyc_9r14_ec2_m7a_xlarge.json +0 -0
  76. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/other/analysis_agner_fog_r9_7900x.json +0 -0
  77. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/other/analysis_uops_info_zen4.json +0 -0
  78. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/other/specs_amd.json +0 -0
  79. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2024_zen5/benchmark/amd_epyc_9r45_ec2_m8a_xlarge.json +0 -0
  80. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2024_zen5/other/analysis_agner_fog_r7_9800x3d.json +0 -0
  81. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/amd/2024_zen5/other/specs_amd.json +0 -0
  82. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/benchmarks/intel_i7_8550U_windows.json +0 -0
  83. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/benchmarks/intel_i7_8700B_macos_github_actions.json +0 -0
  84. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/other/analysis_agner_fog_coffee_lake.json +0 -0
  85. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/other/analysis_uops_info_coffee_lake.json +0 -0
  86. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/benchmarks/intel_xeon_8375c_ice_lake_ec2_m6i_xlarge.json +0 -0
  87. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_agner_fog_ice_lake.json +0 -0
  88. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_uops_info_ice_lake.json +0 -0
  89. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_uops_info_tiger_lake.json +0 -0
  90. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2021_golden_cove_gen_12/benchmarks/intel_xeon_8488c_sapphire_rapids_ec2_m7i_xlarge.json +0 -0
  91. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2021_golden_cove_gen_12/other/analysis_uops_info_alder_lake_p.json +0 -0
  92. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2021_golden_cove_gen_12/other/specs_intel.json +0 -0
  93. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2022_raptor_cove_gen_13_14/benchmarks/intel_xeon_8559c_emerald_rapids_ec2_i7i_xlarge.json +0 -0
  94. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2022_raptor_cove_gen_13_14/other/specs_intel.json +0 -0
  95. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2023_redwood_cove_ultra_1/benchmarks/intel_xeon_6975p_granite_rapids_ec2_m8i_xlarge.json +0 -0
  96. {counted_float-1.0.4 → counted_float-1.1.0}/counted_float/data/x86/intel/2023_redwood_cove_ultra_1/other/specs_intel.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: counted-float
3
- Version: 1.0.4
3
+ Version: 1.1.0
4
4
  Summary: Count floating-point operations in Python code & benchmark relative flop costs.
5
5
  Project-URL: Source, https://github.com/bertpl/counted-float
6
6
  Project-URL: ChangeLog, https://github.com/bertpl/counted-float/blob/main/CHANGELOG.md
@@ -20,9 +20,9 @@ Requires-Dist: numba>=0.50; extra == 'numba'
20
20
  Description-Content-Type: text/markdown
21
21
 
22
22
  ![shields.io-python-versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)
23
- ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.4/badge-test-count.svg)
24
- ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.4/badge-coverage.svg)
25
- ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.4/splash.webp)
23
+ ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/develop/v1.1.0/badge-test-count.svg)
24
+ ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/develop/v1.1.0/badge-coverage.svg)
25
+ ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/develop/v1.1.0/splash.webp)
26
26
 
27
27
  # counted-float
28
28
 
@@ -61,10 +61,12 @@ The `CountedFloat` class is a subclass of `float` and is "contagious", meaning t
61
61
  ensure results of math operations where at least one operand is a `CountedFloat` will also be a `CountedFloat`.
62
62
  This way we ensure flop counting is a 'closed system'.
63
63
 
64
- On top of this, we monkey-patch the `math` module to ensure that all math operations
65
- that require counting (`sqrt`, `log2`, `pow`, ...) are also instrumented.
64
+ On top of this, `math` module functions that require counting (`sqrt`, `log2`, `pow`, ...) are
65
+ also instrumented: while a `FlopCountingContext` is active (see below), they are temporarily
66
+ replaced by counting equivalents. Outside such a context — including at plain `import` time —
67
+ the `math` module is left completely untouched.
66
68
 
67
- **Example 1**:
69
+ **Example**:
68
70
 
69
71
  ```python
70
72
  from counted_float import CountedFloat
@@ -78,18 +80,6 @@ is_float_1 = isinstance(cf, float) # True
78
80
  is_float_2 = isinstance(result, float) # True
79
81
  ```
80
82
 
81
- **Example 2**:
82
-
83
- ```python
84
- import math
85
- from counted_float import CountedFloat
86
-
87
- cf1 = CountedFloat(0.81)
88
-
89
- s = math.sqrt(cf1) # s = CountedFloat(0.9)
90
- is_float = isinstance(s, float) # True
91
- ```
92
-
93
83
  ### The counting model: what gets counted and why
94
84
 
95
85
  The counting model is a contract with two sides:
@@ -118,6 +108,13 @@ The library detects constants through two mechanisms, applying the same rule:
118
108
  The flip side: an unwrapped runtime input is invisible to the counter — that is a wrapping error
119
109
  at your algorithm's boundary, not something the library can detect. When in doubt, wrap.
120
110
 
111
+ One more consequence of the context-scoped `math` patching: `math.*` calls participate in
112
+ counting (and in contagion) only while a `FlopCountingContext` is active. Operator-based
113
+ contagion (`+`, `*`, `**`, ...) works everywhere, but counts are meant to be read through a
114
+ context — so the practical rule is simply: run your measured algorithm inside one.
115
+
116
+ ## 2.2. FLOP counting context managers
117
+
121
118
  Once we use the `CountedFloat` class, we can use the available context managers to count the number of
122
119
  flops performed by `CountedFloat` objects.
123
120
 
@@ -136,7 +133,24 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.ADD: 1}
136
133
  counts.total_count() # 2
137
134
  ```
138
135
 
139
- **Example 2**: _pause counting 1_
136
+ **Example 2**: _math module functions_
137
+
138
+ ```python
139
+ import math
140
+ from counted_float import CountedFloat, FlopCountingContext
141
+
142
+ cf1 = CountedFloat(0.81)
143
+
144
+ with FlopCountingContext() as ctx:
145
+ s = math.sqrt(cf1) # s = CountedFloat(0.9)
146
+
147
+ counts = ctx.flop_counts() # {FlopType.SQRT: 1}
148
+ ```
149
+
150
+ Note that `math.*` functions are only instrumented *inside* the context: outside it,
151
+ `math.sqrt(cf1)` returns a plain `float` and counts nothing.
152
+
153
+ **Example 3**: _pause counting 1_
140
154
 
141
155
  ```python
142
156
  from counted_float import CountedFloat, FlopCountingContext
@@ -155,7 +169,7 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.SUB: 1}
155
169
  counts.total_count() # 2
156
170
  ```
157
171
 
158
- **Example 3**: _pause counting 2_
172
+ **Example 4**: _pause counting 2_
159
173
 
160
174
  ```python
161
175
  from counted_float import CountedFloat, FlopCountingContext, PauseFlopCounting
@@ -546,7 +560,7 @@ CountedFloat is 37.3x slower than float
546
560
 
547
561
  # Appendix A - Flop counting / analysis details
548
562
 
549
- This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. For each flop type, you will find:
563
+ This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. All `math.*` entries below assume an active `FlopCountingContext` (outside one, the `math` module is not instrumented). For each flop type, you will find:
550
564
  - Relevant scalar instructions for ARM (v8+) and x86 (SSE2+)
551
565
  - Python operations that are counted for this flop type
552
566
  - Python operations that are *not* counted for this flop type
@@ -1,7 +1,7 @@
1
1
  ![shields.io-python-versions](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)
2
- ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.4/badge-test-count.svg)
3
- ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.4/badge-coverage.svg)
4
- ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/release/v1.0.4/splash.webp)
2
+ ![genbadge-test-count](https://bertpl.github.io/counted-float/version_artifacts/develop/v1.1.0/badge-test-count.svg)
3
+ ![genbadge-test-coverage](https://bertpl.github.io/counted-float/version_artifacts/develop/v1.1.0/badge-coverage.svg)
4
+ ![counted_float logo](https://bertpl.github.io/counted-float/version_artifacts/develop/v1.1.0/splash.webp)
5
5
 
6
6
  # counted-float
7
7
 
@@ -40,10 +40,12 @@ The `CountedFloat` class is a subclass of `float` and is "contagious", meaning t
40
40
  ensure results of math operations where at least one operand is a `CountedFloat` will also be a `CountedFloat`.
41
41
  This way we ensure flop counting is a 'closed system'.
42
42
 
43
- On top of this, we monkey-patch the `math` module to ensure that all math operations
44
- that require counting (`sqrt`, `log2`, `pow`, ...) are also instrumented.
43
+ On top of this, `math` module functions that require counting (`sqrt`, `log2`, `pow`, ...) are
44
+ also instrumented: while a `FlopCountingContext` is active (see below), they are temporarily
45
+ replaced by counting equivalents. Outside such a context — including at plain `import` time —
46
+ the `math` module is left completely untouched.
45
47
 
46
- **Example 1**:
48
+ **Example**:
47
49
 
48
50
  ```python
49
51
  from counted_float import CountedFloat
@@ -57,18 +59,6 @@ is_float_1 = isinstance(cf, float) # True
57
59
  is_float_2 = isinstance(result, float) # True
58
60
  ```
59
61
 
60
- **Example 2**:
61
-
62
- ```python
63
- import math
64
- from counted_float import CountedFloat
65
-
66
- cf1 = CountedFloat(0.81)
67
-
68
- s = math.sqrt(cf1) # s = CountedFloat(0.9)
69
- is_float = isinstance(s, float) # True
70
- ```
71
-
72
62
  ### The counting model: what gets counted and why
73
63
 
74
64
  The counting model is a contract with two sides:
@@ -97,6 +87,13 @@ The library detects constants through two mechanisms, applying the same rule:
97
87
  The flip side: an unwrapped runtime input is invisible to the counter — that is a wrapping error
98
88
  at your algorithm's boundary, not something the library can detect. When in doubt, wrap.
99
89
 
90
+ One more consequence of the context-scoped `math` patching: `math.*` calls participate in
91
+ counting (and in contagion) only while a `FlopCountingContext` is active. Operator-based
92
+ contagion (`+`, `*`, `**`, ...) works everywhere, but counts are meant to be read through a
93
+ context — so the practical rule is simply: run your measured algorithm inside one.
94
+
95
+ ## 2.2. FLOP counting context managers
96
+
100
97
  Once we use the `CountedFloat` class, we can use the available context managers to count the number of
101
98
  flops performed by `CountedFloat` objects.
102
99
 
@@ -115,7 +112,24 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.ADD: 1}
115
112
  counts.total_count() # 2
116
113
  ```
117
114
 
118
- **Example 2**: _pause counting 1_
115
+ **Example 2**: _math module functions_
116
+
117
+ ```python
118
+ import math
119
+ from counted_float import CountedFloat, FlopCountingContext
120
+
121
+ cf1 = CountedFloat(0.81)
122
+
123
+ with FlopCountingContext() as ctx:
124
+ s = math.sqrt(cf1) # s = CountedFloat(0.9)
125
+
126
+ counts = ctx.flop_counts() # {FlopType.SQRT: 1}
127
+ ```
128
+
129
+ Note that `math.*` functions are only instrumented *inside* the context: outside it,
130
+ `math.sqrt(cf1)` returns a plain `float` and counts nothing.
131
+
132
+ **Example 3**: _pause counting 1_
119
133
 
120
134
  ```python
121
135
  from counted_float import CountedFloat, FlopCountingContext
@@ -134,7 +148,7 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.SUB: 1}
134
148
  counts.total_count() # 2
135
149
  ```
136
150
 
137
- **Example 3**: _pause counting 2_
151
+ **Example 4**: _pause counting 2_
138
152
 
139
153
  ```python
140
154
  from counted_float import CountedFloat, FlopCountingContext, PauseFlopCounting
@@ -525,7 +539,7 @@ CountedFloat is 37.3x slower than float
525
539
 
526
540
  # Appendix A - Flop counting / analysis details
527
541
 
528
- This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. For each flop type, you will find:
542
+ This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. All `math.*` entries below assume an active `FlopCountingContext` (outside one, the `math` module is not instrumented). For each flop type, you will find:
529
543
  - Relevant scalar instructions for ARM (v8+) and x86 (SSE2+)
530
544
  - Python operations that are counted for this flop type
531
545
  - Python operations that are *not* counted for this flop type
@@ -42,6 +42,8 @@ class BenchmarkFloat(MicroBenchmark):
42
42
  # using standard float() arithmetic
43
43
  a = -1e50
44
44
  b = 1e50
45
+ # NOTE: fa/fb are never read (the loop re-evaluates fmid instead); they are kept
46
+ # on purpose so the kernel mimics a realistic bisection workload
45
47
  fa = _zero_function(a)
46
48
  fb = _zero_function(b)
47
49
  while b - a > 1e-15:
@@ -70,6 +72,8 @@ class BenchmarkCountedFloat(MicroBenchmark):
70
72
  # which will make sure all the remaining operations are also executed using CountedFloat arithmetic
71
73
  a = CountedFloat(-1e50)
72
74
  b = CountedFloat(1e50)
75
+ # NOTE: fa/fb are never read (the loop re-evaluates fmid instead); they are kept
76
+ # on purpose so the kernel mimics a realistic bisection workload
73
77
  fa = _zero_function(a)
74
78
  fb = _zero_function(b)
75
79
  while b - a > 1e-15:
@@ -40,7 +40,7 @@ class BuiltInData:
40
40
  raise ValueError(f"No built-in flop weights found for key_filter='{key_filter}'")
41
41
  else:
42
42
  nested_flop_weights_dict = _flat_to_nested_dict(flat_flop_weights_dict)
43
- return _computed_nested_average_flop_weights(nested_flop_weights_dict)
43
+ return _compute_nested_average_flop_weights(nested_flop_weights_dict)
44
44
 
45
45
  @classmethod
46
46
  def get_flop_weights_dict(cls, key_filter: str = "") -> dict[str, FlopWeights]:
@@ -86,11 +86,11 @@ class BuiltInData:
86
86
  # =================================================================================================
87
87
  # Utilities
88
88
  # =================================================================================================
89
- def _computed_nested_average_flop_weights(nested_flop_weights_dict: dict[str, dict | FlopWeights]) -> FlopWeights:
89
+ def _compute_nested_average_flop_weights(nested_flop_weights_dict: dict[str, dict | FlopWeights]) -> FlopWeights:
90
90
  # make sure all values of the dict are FlopWeights instances
91
91
  for key, value in nested_flop_weights_dict.items():
92
92
  if isinstance(value, dict):
93
- nested_flop_weights_dict[key] = _computed_nested_average_flop_weights(value)
93
+ nested_flop_weights_dict[key] = _compute_nested_average_flop_weights(value)
94
94
 
95
95
  # now we can average all FlopWeights instances
96
96
  return FlopWeights.as_geo_mean(list(nested_flop_weights_dict.values()))
@@ -121,10 +121,6 @@ def _load_json_files_as_dict(resource_root) -> dict[str, str]:
121
121
  'specs.x86.intel_core_i9_13900k'
122
122
  """
123
123
 
124
- # allow both plain & recursive calls
125
- # if resource_root is None:
126
- # resource_root = files("counted_float._core.data")
127
-
128
124
  # crawl entire folder structure
129
125
  result = {}
130
126
  for entry in resource_root.iterdir():
@@ -211,6 +207,7 @@ class FlopWeightsTreeView:
211
207
  child.lst_is_leaf,
212
208
  child.lst_tree_str,
213
209
  child.lst_flop_weights,
210
+ strict=True,
214
211
  )
215
212
  ):
216
213
  self.lst_indent.append(1 + indent)
@@ -238,7 +235,6 @@ class FlopWeightsTreeView:
238
235
  tree_width = 5 + max([len(line) for line in self.lst_tree_str])
239
236
  col_width = 10
240
237
  sorted_flop_types = self.lst_flop_weights[0].get_sorted_flop_types()
241
- max_indent = max(self.lst_indent)
242
238
 
243
239
  n_cols_per_block = max(1, int((console_width - tree_width) / col_width))
244
240
  flop_types_per_block = [
@@ -260,6 +256,7 @@ class FlopWeightsTreeView:
260
256
  self.lst_is_leaf,
261
257
  self.lst_tree_str,
262
258
  self.lst_flop_weights,
259
+ strict=True,
263
260
  ):
264
261
  line = tree_str.ljust(tree_width)
265
262
  for flop_type in flop_types:
@@ -4,6 +4,7 @@ as well as providing .pause() and .resume() methods to control flop counting.
4
4
  """
5
5
 
6
6
  from counted_float._core.counting._global_counter import GLOBAL_COUNTER
7
+ from counted_float._core.counting._math_patching import apply_math_patches, remove_math_patches
7
8
  from counted_float._core.models import FlopCounts
8
9
 
9
10
 
@@ -15,10 +16,6 @@ class FlopCountingContext:
15
16
  Context manager that can be used to count FLOP operations in a block of code. Only floating-point
16
17
  operations of CountedFloat objects are counted. So make sure all math uses this type.
17
18
 
18
- Flops need to be registered by either of the following:
19
- - calls to register_flops(...)
20
- - using CountedFloat() objects in the computations
21
-
22
19
  LIMITATIONS:
23
20
  - this context manager is not thread-safe
24
21
  - not _all_ floating-point operations are counted, see the docs for more details.
@@ -71,11 +68,15 @@ class FlopCountingContext:
71
68
  # Context manager interface
72
69
  # -------------------------------------------------------------------------
73
70
  def __enter__(self):
71
+ # patching the math module is tied to the with-block lifetime (not to pause()/resume(),
72
+ # which only control whether counts are registered)
73
+ apply_math_patches()
74
74
  self.resume()
75
75
  return self
76
76
 
77
77
  def __exit__(self, exc_type, exc_val, exc_tb):
78
78
  self.pause()
79
+ remove_math_patches()
79
80
 
80
81
 
81
82
  # =================================================================================================
@@ -0,0 +1,205 @@
1
+ from __future__ import annotations
2
+
3
+ from ._global_counter import GLOBAL_COUNTER
4
+
5
+
6
+ class CountedFloat(float):
7
+ # -------------------------------------------------------------------------
8
+ # CONSTRUCTOR
9
+ # -------------------------------------------------------------------------
10
+ def __new__(cls, value: float | int):
11
+ if isinstance(value, int):
12
+ GLOBAL_COUNTER.incr_i2f()
13
+ self = super().__new__(cls, float(value))
14
+ return self
15
+
16
+ def __str__(self):
17
+ return self.__repr__()
18
+
19
+ def __repr__(self):
20
+ return f"CountedFloat({super().__repr__()})"
21
+
22
+ def __hash__(self):
23
+ return super().__hash__()
24
+
25
+ # -------------------------------------------------------------------------
26
+ # OVERLOADED MATH OPERATIONS
27
+ # -------------------------------------------------------------------------
28
+ def __abs__(self) -> CountedFloat:
29
+ """abs(x)"""
30
+ GLOBAL_COUNTER.incr_abs()
31
+ return CountedFloat(super().__abs__())
32
+
33
+ def __neg__(self) -> CountedFloat:
34
+ """-x"""
35
+ GLOBAL_COUNTER.incr_minus()
36
+ return CountedFloat(super().__neg__())
37
+
38
+ def __eq__(self, other) -> bool:
39
+ """x==other or other==x"""
40
+ if isinstance(other, int):
41
+ GLOBAL_COUNTER.incr_i2f()
42
+ GLOBAL_COUNTER.incr_comp()
43
+ return super().__eq__(other)
44
+
45
+ def __ne__(self, other) -> bool:
46
+ """x!=other or other!=x"""
47
+ if isinstance(other, int):
48
+ GLOBAL_COUNTER.incr_i2f()
49
+ GLOBAL_COUNTER.incr_comp()
50
+ return super().__ne__(other)
51
+
52
+ def __lt__(self, other):
53
+ """x<other"""
54
+ if isinstance(other, int):
55
+ GLOBAL_COUNTER.incr_i2f()
56
+ GLOBAL_COUNTER.incr_comp()
57
+ return super().__lt__(other)
58
+
59
+ def __le__(self, other):
60
+ """x<=other"""
61
+ if isinstance(other, int):
62
+ GLOBAL_COUNTER.incr_i2f()
63
+ GLOBAL_COUNTER.incr_comp()
64
+ return super().__le__(other)
65
+
66
+ def __gt__(self, other):
67
+ """x>other"""
68
+ if isinstance(other, int):
69
+ GLOBAL_COUNTER.incr_i2f()
70
+ GLOBAL_COUNTER.incr_comp()
71
+ return super().__gt__(other)
72
+
73
+ def __ge__(self, other):
74
+ """x>=other"""
75
+ if isinstance(other, int):
76
+ GLOBAL_COUNTER.incr_i2f()
77
+ GLOBAL_COUNTER.incr_comp()
78
+ return super().__ge__(other)
79
+
80
+ def __round__(self, n=None) -> int:
81
+ """
82
+ round(x, n)
83
+ n = None -> round to nearest integer and return int
84
+ n = 0 -> round to nearest integer and return float
85
+ n > 0 -> round to n decimal places and return float
86
+ """
87
+ if n is None:
88
+ GLOBAL_COUNTER.incr_f2i() # will round and return int
89
+ else:
90
+ GLOBAL_COUNTER.incr_rnd() # will round and return float
91
+
92
+ return super().__round__(n)
93
+
94
+ def __floor__(self) -> int:
95
+ """math.floor(x)"""
96
+ GLOBAL_COUNTER.incr_f2i()
97
+ return super().__floor__()
98
+
99
+ def __ceil__(self) -> int:
100
+ """math.ceil(x)"""
101
+ GLOBAL_COUNTER.incr_f2i()
102
+ return super().__ceil__()
103
+
104
+ def __int__(self) -> int:
105
+ """int(x)"""
106
+ GLOBAL_COUNTER.incr_f2i()
107
+ return super().__int__()
108
+
109
+ def __trunc__(self) -> int:
110
+ """int(x)"""
111
+ GLOBAL_COUNTER.incr_f2i()
112
+ return super().__trunc__()
113
+
114
+ def __add__(self, other) -> CountedFloat:
115
+ """x+other"""
116
+ GLOBAL_COUNTER.incr_add()
117
+ if isinstance(other, int):
118
+ GLOBAL_COUNTER.incr_i2f()
119
+ return CountedFloat(super().__add__(other))
120
+
121
+ def __radd__(self, other) -> CountedFloat:
122
+ """other+x"""
123
+ GLOBAL_COUNTER.incr_add()
124
+ if isinstance(other, int):
125
+ GLOBAL_COUNTER.incr_i2f()
126
+ return CountedFloat(super().__radd__(other))
127
+
128
+ def __sub__(self, other) -> CountedFloat:
129
+ """x-other"""
130
+ GLOBAL_COUNTER.incr_sub()
131
+ if isinstance(other, int):
132
+ GLOBAL_COUNTER.incr_i2f()
133
+ return CountedFloat(super().__sub__(other))
134
+
135
+ def __rsub__(self, other) -> CountedFloat:
136
+ """other-x"""
137
+ GLOBAL_COUNTER.incr_sub()
138
+ if isinstance(other, int):
139
+ GLOBAL_COUNTER.incr_i2f()
140
+ return CountedFloat(super().__rsub__(other))
141
+
142
+ def __mul__(self, other) -> CountedFloat:
143
+ """x*other or other*x"""
144
+ GLOBAL_COUNTER.incr_mul()
145
+ if isinstance(other, int):
146
+ GLOBAL_COUNTER.incr_i2f()
147
+ return CountedFloat(super().__mul__(other))
148
+
149
+ def __rmul__(self, other) -> CountedFloat:
150
+ """other*x"""
151
+ GLOBAL_COUNTER.incr_mul()
152
+ if isinstance(other, int):
153
+ GLOBAL_COUNTER.incr_i2f()
154
+ return CountedFloat(super().__rmul__(other))
155
+
156
+ def __truediv__(self, other) -> CountedFloat:
157
+ """x/other"""
158
+ GLOBAL_COUNTER.incr_div()
159
+ if isinstance(other, int):
160
+ GLOBAL_COUNTER.incr_i2f()
161
+ return CountedFloat(super().__truediv__(other))
162
+
163
+ def __rtruediv__(self, other) -> CountedFloat:
164
+ """other/x"""
165
+ GLOBAL_COUNTER.incr_div()
166
+ if isinstance(other, int):
167
+ GLOBAL_COUNTER.incr_i2f()
168
+ return CountedFloat(super().__rtruediv__(other))
169
+
170
+ def __pow__(self, other) -> CountedFloat:
171
+ """
172
+ x**other
173
+
174
+ Counting heuristic: an `int` operand is taken as evidence of a hardcoded constant in the
175
+ source (ints don't fall out of floating-point computations), so `x**2` counts as MUL —
176
+ the strength reduction (x*x) a compiled port would apply. A float operand may just as well
177
+ be a runtime variable that happens to hold that value, where a port would compile a
178
+ generic pow, so `x**2.0` counts as POW.
179
+ """
180
+ if isinstance(other, int) and other == 2:
181
+ GLOBAL_COUNTER.incr_mul() # x^2 = x*x
182
+ else:
183
+ if isinstance(other, int):
184
+ GLOBAL_COUNTER.incr_i2f()
185
+ GLOBAL_COUNTER.incr_pow()
186
+ return CountedFloat(super().__pow__(other))
187
+
188
+ def __rpow__(self, other) -> CountedFloat:
189
+ """
190
+ other**x
191
+
192
+ Same constant-detection heuristic as __pow__, applied to the base: an `int` base 2 or 10
193
+ is taken as a hardcoded constant, counting as EXP2 / EXP10 (the strength reduction a
194
+ compiled port would apply); a float base may be a runtime variable, so it counts as
195
+ generic POW.
196
+ """
197
+ if isinstance(other, int) and other == 2:
198
+ GLOBAL_COUNTER.incr_exp2()
199
+ elif isinstance(other, int) and other == 10:
200
+ GLOBAL_COUNTER.incr_exp10()
201
+ else:
202
+ if isinstance(other, int):
203
+ GLOBAL_COUNTER.incr_i2f()
204
+ GLOBAL_COUNTER.incr_pow()
205
+ return CountedFloat(super().__rpow__(other))