ID photo of Ciro Santilli taken in 2013 right eyeCiro Santilli OurBigBook logoOurBigBook.com  Sponsor 中国独裁统治 China Dictatorship 新疆改造中心、六四事件、法轮功、郝海东、709大抓捕、2015巴拿马文件 邓家贵、低端人口、西藏骚乱
euler/972.py
#!/usr/bin/env python

'''
By GPT-5. Runtime: 53.2s on pypy3 3.11.13, Ubuntu 25.10, Lenovo ThinkPad P14s.
'''

# Save as solve_euler_972.py and run: python solve_euler_972.py
from math import gcd
from collections import defaultdict
from math import lcm
import time

def rationals_pairs(N):
    lst = []
    for d in range(1, N+1):
        for n in range(-d+1, d):
            if gcd(abs(n), d) == 1:
                lst.append((n, d))
    return lst

def build_points(N):
    X = rationals_pairs(N)
    pts = []
    # For each pair of rational coords (x = nx/dx, y = ny/dy) keep if inside unit circle
    for nx, dx in X:
        dx2 = dx*dx
        for ny, dy in X:
            dy2 = dy*dy
            if nx*nx * dy2 + ny*ny * dx2 < dx2 * dy2:
                pts.append((nx, dx, ny, dy))
    return pts

def to_scaled_components(nx1,dx1, ny1,dy1, nx2,dx2, ny2,dy2):
    D1 = lcm(dx1, dy1)
    X1 = nx1 * (D1 // dx1)
    Y1 = ny1 * (D1 // dy1)
    D2 = lcm(dx2, dy2)
    X2 = nx2 * (D2 // dx2)
    Y2 = ny2 * (D2 // dy2)
    D = lcm(D1, D2)
    mul1 = D // D1
    mul2 = D // D2
    X1 *= mul1; Y1 *= mul1
    X2 *= mul2; Y2 *= mul2
    return X1, Y1, X2, Y2, D

def normalized_dir_from_scaled(X, Y):
    xi = X; yi = Y
    if xi == 0 and yi == 0:
        return (0,0)
    g = gcd(abs(xi), abs(yi))
    xi //= g; yi //= g
    if xi < 0 or (xi == 0 and yi < 0):
        xi = -xi; yi = -yi
    return (xi, yi)

def geodesic_id_fast_int(p, q):
    nx1,dx1, ny1,dy1 = p
    nx2,dx2, ny2,dy2 = q
    X1, Y1, X2, Y2, D = to_scaled_components(nx1,dx1, ny1,dy1, nx2,dx2, ny2,dy2)
    cross = X1 * Y2 - Y1 * X2
    if cross == 0:
        if X1 == 0 and Y1 == 0:
            dirpair = normalized_dir_from_scaled(X2, Y2)
        else:
            dirpair = normalized_dir_from_scaled(X1, Y1)
        return ('L', dirpair)
    # R1 = X1^2 + Y1^2 + D^2 ; R2 similar
    R1 = X1*X1 + Y1*Y1 + D*D
    R2 = X2*X2 + Y2*Y2 + D*D
    # integer numerators for center formula (see explanation)
    numx = R1 * Y2 - Y1 * R2
    numy = X1 * R2 - R1 * X2
    den = 2 * D * cross
    if den < 0:
        den = -den; numx = -numx; numy = -numy
    g = gcd(gcd(abs(numx), abs(numy)), den)
    if g > 1:
        numx //= g; numy //= g; den //= g
    return ('C', (numx, numy, den))

def compute_T(N, show_stats=False):
    pts = build_points(N)
    n = len(pts)
    start = time.time()
    geods = {}
    for i in range(n):
        p = pts[i]
        for j in range(i+1, n):
            gid = geodesic_id_fast_int(p, pts[j])
            if gid in geods:
                s = geods[gid]
                s.add(i); s.add(j)
            else:
                geods[gid] = {i, j}
    total = 0
    for s in geods.values():
        k = len(s)
        if k >= 3:
            total += k*(k-1)*(k-2)
    if show_stats:
        print("points:", n, "distinct geodesics:", len(geods), "time:", time.time()-start)
    return total

if __name__ == "__main__":
    assert compute_T(2) == 24
    assert compute_T(3) == 1296
    print(compute_T(12))