Skip to content

Commit a3733b3

Browse files
RobLe3claude
andcommitted
ci: fix formatting and linting issues
- Run black formatter on all source and test files - Fix ruff violations (imports, typing, whitespace) - Update ruff config to use lint.* sections - Add fail-fast: false to CI matrix for better diagnostics - Add tool version printing step for Python 3.12 job 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 2d4b869 commit a3733b3

24 files changed

Lines changed: 367 additions & 273 deletions

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
test:
1111
runs-on: ubuntu-latest
1212
strategy:
13+
fail-fast: false
1314
matrix:
1415
python-version: ["3.10", "3.11", "3.12"]
1516

@@ -26,6 +27,15 @@ jobs:
2627
python -m pip install --upgrade pip
2728
pip install -e ".[dev]"
2829
30+
- name: Print tool versions
31+
run: |
32+
python --version
33+
pip --version
34+
black --version
35+
ruff --version
36+
pytest --version
37+
if: matrix.python-version == '3.12'
38+
2939
- name: Run tests
3040
run: |
3141
pytest -v

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ include = '\.pyi?$'
7171
[tool.ruff]
7272
line-length = 100
7373
target-version = "py310"
74+
75+
[tool.ruff.lint]
7476
select = [
7577
"E", # pycodestyle errors
7678
"W", # pycodestyle warnings

src/lulzprime/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
__version__ = "0.1.0"
2424

2525
# Public API exports
26-
from .resolve import resolve, between, next_prime, prev_prime
26+
from .batch import between_many, resolve_many
2727
from .forecast import forecast
2828
from .primality import is_prime
29+
from .resolve import between, next_prime, prev_prime, resolve
2930
from .simulator import simulate
30-
from .batch import resolve_many, between_many
3131

3232
__all__ = [
3333
"resolve",

src/lulzprime/batch.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
Canonical reference: https://roblemumin.com/library.html
1010
"""
1111

12-
from typing import Iterable
13-
from .utils import validate_index, validate_range
12+
from collections.abc import Iterable
13+
1414
from .lookup import resolve_internal_with_pi
15-
from .resolve import between as _between
1615
from .pi import pi as default_pi
16+
from .resolve import between as _between
17+
from .utils import validate_index
1718

1819

1920
def resolve_many(indices: Iterable[int]) -> list[int]:
@@ -162,9 +163,13 @@ def between_many(ranges: Iterable[tuple[int, int]]) -> list[list[int]]:
162163
for i, range_tuple in enumerate(ranges_list):
163164
# Validate tuple structure
164165
if not isinstance(range_tuple, tuple):
165-
raise TypeError(f"Range at position {i} must be a tuple, got {type(range_tuple).__name__}")
166+
raise TypeError(
167+
f"Range at position {i} must be a tuple, got {type(range_tuple).__name__}"
168+
)
166169
if len(range_tuple) != 2:
167-
raise ValueError(f"Range at position {i} must be a 2-tuple (x, y), got length {len(range_tuple)}")
170+
raise ValueError(
171+
f"Range at position {i} must be a 2-tuple (x, y), got length {len(range_tuple)}"
172+
)
168173

169174
x, y = range_tuple
170175

@@ -176,5 +181,3 @@ def between_many(ranges: Iterable[tuple[int, int]]) -> list[list[int]]:
176181
raise type(e)(f"Invalid range at position {i}: {e}") from None
177182

178183
return results
179-
180-

src/lulzprime/config.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,31 @@
1111
# Small primes cache for optimization
1212
# These are used for quick lookups and divisibility checks
1313
SMALL_PRIMES = [
14-
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
15-
53, 59, 61, 67, 71, 73, 79, 83, 89, 97
14+
2,
15+
3,
16+
5,
17+
7,
18+
11,
19+
13,
20+
17,
21+
19,
22+
23,
23+
29,
24+
31,
25+
37,
26+
41,
27+
43,
28+
47,
29+
53,
30+
59,
31+
61,
32+
67,
33+
71,
34+
73,
35+
79,
36+
83,
37+
89,
38+
97,
1639
]
1740

1841
# Forecast thresholds

src/lulzprime/diagnostics.py

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
IMPORTANT: Diagnostics must observe only. They must never alter computational results.
1010
"""
1111

12-
from typing import Any, Callable
13-
from dataclasses import dataclass, field
12+
from collections.abc import Callable
13+
from dataclasses import dataclass
14+
from typing import Any
1415

1516

1617
def verify_resolution(result: int, index: int, pi_func: Callable, is_prime_func: Callable) -> bool:
@@ -35,9 +36,7 @@ def verify_resolution(result: int, index: int, pi_func: Callable, is_prime_func:
3536
"""
3637
# Check primality
3738
if not is_prime_func(result):
38-
raise AssertionError(
39-
f"Resolution verification failed: result {result} is not prime"
40-
)
39+
raise AssertionError(f"Resolution verification failed: result {result} is not prime")
4140

4241
# Check index
4342
pi_result = pi_func(result)
@@ -78,15 +77,15 @@ def verify_range(primes: list[int], is_prime_func: Callable) -> bool:
7877

7978
# Check strictly increasing
8079
for i in range(1, len(primes)):
81-
if primes[i] <= primes[i-1]:
82-
raise AssertionError(
83-
f"Range verification failed: not strictly increasing at index {i}"
84-
)
80+
if primes[i] <= primes[i - 1]:
81+
raise AssertionError(f"Range verification failed: not strictly increasing at index {i}")
8582

8683
return True
8784

8885

89-
def check_forecast_quality(index: int, forecast_value: int, pi_func: Callable, epsilon: float = 0.1) -> dict[str, Any]:
86+
def check_forecast_quality(
87+
index: int, forecast_value: int, pi_func: Callable, epsilon: float = 0.1
88+
) -> dict[str, Any]:
9089
"""
9190
Check forecast quality (Tier C sanity check).
9291
@@ -109,10 +108,10 @@ def check_forecast_quality(index: int, forecast_value: int, pi_func: Callable, e
109108
relative_error = abs(pi_forecast - index) / index
110109

111110
return {
112-
'passed': relative_error < epsilon,
113-
'relative_error': relative_error,
114-
'pi_forecast': pi_forecast,
115-
'threshold': epsilon,
111+
"passed": relative_error < epsilon,
112+
"relative_error": relative_error,
113+
"pi_forecast": pi_forecast,
114+
"threshold": epsilon,
116115
}
117116

118117

@@ -132,10 +131,9 @@ def simulator_diagnostics(sequence: list[int], pi_func: Callable) -> dict[str, A
132131
Returns:
133132
Dictionary with diagnostic metrics
134133
"""
135-
import math
136134

137135
if not sequence:
138-
return {'error': 'empty sequence'}
136+
return {"error": "empty sequence"}
139137

140138
n = len(sequence)
141139
q_final = sequence[-1]
@@ -151,24 +149,26 @@ def simulator_diagnostics(sequence: list[int], pi_func: Callable) -> dict[str, A
151149
q_i = sequence[i]
152150
pi_i = pi_func(q_i)
153151
ratio_i = pi_i / (i + 1)
154-
checkpoints.append({
155-
'step': i + 1,
156-
'q': q_i,
157-
'pi': pi_i,
158-
'density_ratio': ratio_i,
159-
})
152+
checkpoints.append(
153+
{
154+
"step": i + 1,
155+
"q": q_i,
156+
"pi": pi_i,
157+
"density_ratio": ratio_i,
158+
}
159+
)
160160

161161
# Compute drift (deviation from expected ratio of 1.0)
162162
drift = abs(density_ratio - 1.0)
163163

164164
return {
165-
'n_steps': n,
166-
'q_final': q_final,
167-
'pi_final': pi_final,
168-
'density_ratio': density_ratio,
169-
'drift': drift,
170-
'convergence_acceptable': drift < 0.15, # Threshold from paper
171-
'checkpoints': checkpoints,
165+
"n_steps": n,
166+
"q_final": q_final,
167+
"pi_final": pi_final,
168+
"density_ratio": density_ratio,
169+
"drift": drift,
170+
"convergence_acceptable": drift < 0.15, # Threshold from paper
171+
"checkpoints": checkpoints,
172172
}
173173

174174

@@ -188,6 +188,7 @@ class ResolveStats:
188188
forecast_value: Initial forecast estimate
189189
final_result: Final resolved prime value
190190
"""
191+
191192
pi_calls: int = 0
192193
binary_search_iterations: int = 0
193194
correction_backward_steps: int = 0
@@ -222,14 +223,15 @@ def set_result(self, value: int) -> None:
222223
def to_dict(self) -> dict[str, Any]:
223224
"""Convert stats to dictionary for reporting."""
224225
return {
225-
'pi_calls': self.pi_calls,
226-
'binary_search_iterations': self.binary_search_iterations,
227-
'correction_backward_steps': self.correction_backward_steps,
228-
'correction_forward_steps': self.correction_forward_steps,
229-
'forecast_value': self.forecast_value,
230-
'final_result': self.final_result,
226+
"pi_calls": self.pi_calls,
227+
"binary_search_iterations": self.binary_search_iterations,
228+
"correction_backward_steps": self.correction_backward_steps,
229+
"correction_forward_steps": self.correction_forward_steps,
230+
"forecast_value": self.forecast_value,
231+
"final_result": self.final_result,
231232
}
232233

234+
233235
@dataclass
234236
class MeisselStats:
235237
"""
@@ -245,6 +247,7 @@ class MeisselStats:
245247
recursion_depth_max: Maximum recursion depth reached
246248
recursion_guard_trips: Number of times recursion guard triggered
247249
"""
250+
248251
phi_calls: int = 0
249252
phi_cache_size: int = 0
250253
pi_cache_size: int = 0
@@ -274,9 +277,9 @@ def increment_recursion_guard_trips(self) -> None:
274277
def to_dict(self) -> dict[str, Any]:
275278
"""Convert stats to dictionary for reporting."""
276279
return {
277-
'phi_calls': self.phi_calls,
278-
'phi_cache_size': self.phi_cache_size,
279-
'pi_cache_size': self.pi_cache_size,
280-
'recursion_depth_max': self.recursion_depth_max,
281-
'recursion_guard_trips': self.recursion_guard_trips,
280+
"phi_calls": self.phi_calls,
281+
"phi_cache_size": self.phi_cache_size,
282+
"pi_cache_size": self.pi_cache_size,
283+
"recursion_depth_max": self.recursion_depth_max,
284+
"recursion_guard_trips": self.recursion_guard_trips,
282285
}

src/lulzprime/forecast.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
Output class: Tier C (Estimate only)
99
"""
1010

11-
from .config import SMALL_PRIMES, FORECAST_SMALL_THRESHOLD
12-
from .utils import log_n, log_log_n, validate_index
11+
from .config import FORECAST_SMALL_THRESHOLD, SMALL_PRIMES
12+
from .utils import log_log_n, log_n, validate_index
1313

1414

1515
def forecast(index: int) -> int:

src/lulzprime/gaps.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
Canonical reference: https://roblemumin.com/library.html
88
"""
99

10-
from typing import Optional
11-
1210

1311
def get_empirical_gap_distribution(
1412
max_gap: int = 100,
@@ -96,7 +94,7 @@ def tilt_gap_distribution(
9694

9795
def sample_gap(
9896
distribution: dict[int, float],
99-
rng: Optional[object] = None,
97+
rng: object | None = None,
10098
) -> int:
10199
"""
102100
Sample a gap from the given distribution.

src/lulzprime/lehmer.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
"""
4747

4848
import math
49-
from functools import lru_cache
5049

5150

5251
def _simple_sieve(limit: int) -> list[int]:
@@ -217,11 +216,11 @@ def _integer_cube_root(x: int) -> int:
217216
return 1
218217

219218
# Initial guess using floating-point (will be refined)
220-
k = int(x ** (1/3))
219+
k = int(x ** (1 / 3))
221220

222221
# Refine using Newton's method with integer arithmetic
223222
# Ensures we find the exact integer cube root
224-
while k ** 3 > x:
223+
while k**3 > x:
225224
k -= 1
226225
while (k + 1) ** 3 <= x:
227226
k += 1

src/lulzprime/lookup.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
Canonical reference: https://roblemumin.com/library.html
88
"""
99

10-
from typing import Callable, Optional
10+
from collections.abc import Callable
11+
12+
from .diagnostics import ResolveStats
1113
from .forecast import forecast
1214
from .pi import pi
13-
from .primality import is_prime, prev_prime, next_prime
14-
from .diagnostics import ResolveStats
15+
from .primality import is_prime, next_prime, prev_prime
1516

1617

1718
def resolve_internal(index: int) -> int:
@@ -37,9 +38,7 @@ def resolve_internal(index: int) -> int:
3738

3839

3940
def resolve_internal_with_pi(
40-
index: int,
41-
pi_fn: Callable[[int], int],
42-
stats: Optional[ResolveStats] = None
41+
index: int, pi_fn: Callable[[int], int], stats: ResolveStats | None = None
4342
) -> int:
4443
"""
4544
Internal resolution pipeline with injected π(x) function.
@@ -67,9 +66,11 @@ def resolve_internal_with_pi(
6766

6867
# Wrap pi_fn to count calls if stats is enabled
6968
if stats:
69+
7070
def counted_pi_fn(x: int) -> int:
7171
stats.increment_pi_calls()
7272
return pi_fn(x)
73+
7374
else:
7475
counted_pi_fn = pi_fn
7576

@@ -113,7 +114,7 @@ def _binary_search_pi(
113114
target_index: int,
114115
guess: int,
115116
pi_fn: Callable[[int], int] = pi,
116-
stats: Optional[ResolveStats] = None
117+
stats: ResolveStats | None = None,
117118
) -> int:
118119
"""
119120
Binary search to find minimal x where π(x) >= target_index.
@@ -132,7 +133,7 @@ def _binary_search_pi(
132133
# to reduce binary search iterations. This cuts search space by ~2x.
133134
# Conservative bounds: 5% margin on each side (safer than analytic formula)
134135
lo = max(2, int(guess * 0.95)) # 5% below forecast
135-
hi = int(guess * 1.05) # 5% above forecast
136+
hi = int(guess * 1.05) # 5% above forecast
136137

137138
# Adjust if initial bounds are wrong
138139
if pi_fn(lo) > target_index:

0 commit comments

Comments
 (0)