;;; ursa.lisp.txt — Zoë Trout's favorite programs. ;;; ;;; Name kept from Zoë's canonical file at https://wedgewack.org/ursa.lisp.txt — ;;; same programs, ported to idiomatic Scheme for lumbda. Every iteration ;;; here is a named-let tail call, so TCO keeps the stack flat and portal ;;; determinism stays intact. See ticket 0004 and whitepaper §9.2 for the ;;; CL compat path (loop macro + setf/defun shim) that runs Zoë's original ;;; CL source unchanged. ;;; ;;; Load: (load "examples/ursa.lisp.txt") ;;; ── modular exponentiation ─────────────────────────────────────── ;;; (expt-mod b e m) = b^e mod m, in O(log e) multiplies. (define (expt-mod base exponent modulus) (cond ((= modulus 1) 0) (else (let loop ((b (modulo base modulus)) (e exponent) (acc 1)) (cond ((zero? e) acc) ((odd? e) (loop (modulo (* b b) modulus) (quotient e 2) (modulo (* acc b) modulus))) (else (loop (modulo (* b b) modulus) (quotient e 2) acc))))))) ;;; ── Miller-Rabin primality ─────────────────────────────────────── (define (miller-rabin-base n a d s) (let ((x (expt-mod a d n))) (if (or (= x 1) (= x (- n 1))) #t (let loop ((i 0) (x x)) (cond ((>= i s) #f) (else (let ((x2 (expt-mod x 2 n))) (if (= x2 (- n 1)) #t (loop (+ i 1) x2))))))))) ;;; n-1 = 2^s * d with d odd, then k witness trials. (define (miller-rabin-k n k) (cond ((<= n 1) #f) ((<= n 3) #t) ((even? n) #f) (else (let decompose ((d (- n 1)) (s 0)) (if (even? d) (decompose (quotient d 2) (+ s 1)) (let witness ((i 0)) (cond ((>= i k) #t) (else (let ((a (+ 2 (random-int (- n 2))))) (if (miller-rabin-base n a d s) (witness (+ i 1)) #f)))))))))) ;;; Variadic wrapper — k defaults to 10. (define (miller-rabin n . opt) (miller-rabin-k n (if (null? opt) 10 (car opt)))) ;;; (primep n) returns n if prime, #f otherwise — matches Zoë's CL shape. (define (primep n . opt) (let ((k (if (null? opt) 10 (car opt)))) (if (miller-rabin-k n k) n #f))) ;;; ── Pollard rho factorization ──────────────────────────────────── ;;; rhoff: one factor-finding attempt on n. Returns a non-trivial factor ;;; or #f if the tortoise/hare cycled before separating. (define (rhoff n) (cond ((even? n) 2) ((primep n) n) (else (let ((c (+ 1 (random-int (- n 1))))) (define (f z) (modulo (+ (* z z) c) n)) ;; Birthday-bound iteration cap. Pollard rho expects ≈√n ;; iterations before a collision. Capping at 4·√n + 32 ;; lets honest runs finish while rejecting pathological c ;; values quickly; rho's outer retry draws a new c and ;; keeps the total work bounded. Without this, a bad c ;; can loop for ~n iters allocating let* bindings each ;; time, which on the non-GC asm tier builds up heap. (let ((cap (+ 32 (* 4 (isqrt n))))) (let loop ((x 2) (y 2) (d 1) (k 0)) (cond ((> k cap) #f) ((not (= d 1)) (if (and (< 1 d) (< d n)) d #f)) (else (let* ((x2 (f x)) (y2 (f (f y))) (d2 (gcd (abs (- x2 y2)) n))) (loop x2 y2 d2 (+ k 1))))))))))) ;;; rho: full factorization via a work-list. Zoë's CL uses an adjustable ;;; vector with vector-push-extend; the Scheme version uses a list as a ;;; stack (cons = push, car+cdr = pop). Same algorithm, no new mutable ;;; type needed — so lumbda's portal continues to serialize cleanly. (define (rho n) (and (> n 1) (let loop ((pending (list n)) (results '())) (cond ((null? pending) (sort results)) (else (let ((m (car pending)) (rest (cdr pending))) (cond ((= m 1) (loop rest results)) ((primep m) (loop rest (cons m results))) ((even? m) (loop (cons (quotient m 2) rest) (cons 2 results))) (else (let retry ((d (rhoff m))) (if d (loop (cons d (cons (quotient m d) rest)) results) (retry (rhoff m)))))))))))) (define (factor n) (rho n)) ;;; ── repunit-value ──────────────────────────────────────────────── ;;; digit * sum_{i=0..n-1} base^i — a repunit in the given base. (define (repunit-value n . opt) (let* ((digit (if (null? opt) 1 (car opt))) (base (if (or (null? opt) (null? (cdr opt))) 2 (cadr opt)))) (and (> base digit) (let loop ((i 1) (j digit)) (if (> i (- n 1)) j (loop (+ i 1) (+ j (* digit (expt base i))))))))) ;;; ── Mersenne + Lucas-Lehmer ────────────────────────────────────── (define (mersenne-number p) (- (expt 2 p) 1)) ;;; Lucas-Lehmer recurrence: s_0=4, s_{i+1} = s_i^2 - 2 (mod M_p). ;;; Returns s_{p-2}. M_p is prime iff the residue is zero. (define (lucas-lehmer-residue p) (let ((m (mersenne-number p))) (let loop ((i 0) (s 4)) (if (>= i (- p 2)) s (loop (+ i 1) (modulo (- (* s s) 2) m)))))) (define (lucas-lehmer-primep p) (and (primep p) (zero? (lucas-lehmer-residue p)))) ;;; ── digit conversion ───────────────────────────────────────────── ;;; Zoë's CL version uses defgeneric/defmethod to dispatch on type ;;; (integer → vector, vector → integer, sequence → integer via coerce). ;;; In lumbda we dispatch by predicate — same behavior, no CLOS needed, ;;; no new namespace to serialize. Portal stays clean. (define (digits->integer seq base) (let* ((vec (if (vector? seq) seq (list->vector seq))) (len (vector-length vec))) (let loop ((i 0) (result 0)) (if (>= i len) result (loop (+ i 1) (+ (* result base) (vector-ref vec i))))))) (define (integer->digits number base) (cond ((< number base) (vector number)) (else (let loop ((n number) (acc '())) (if (zero? n) (list->vector acc) (loop (quotient n base) (cons (modulo n base) acc))))))) (define (digits number base) (cond ((integer? number) (integer->digits number base)) ((vector? number) (digits->integer number base)) ((list? number) (digits->integer number base)) (else (error "digits: expected integer, vector, or list")))) ;;; ── random-n-bit integer primitives ────────────────────────────── ;;; Uniformly random integer with the top bit set — always in ;;; [2^{n-1}, 2^n). Matches Zoë's CL `of-n-bits`. (define (of-n-bits n) (and (>= n 2) (let loop ((i (- n 2)) (sum (expt 2 (- n 1)))) (if (< i 0) sum (loop (- i 1) (+ sum (* (random-int 2) (expt 2 i)))))))) ;;; Rejection sample: draw random n-bit integers until one is prime. (define (prime-of-n-bits n) (and (>= n 2) (let try () (let ((candidate (of-n-bits n))) (if (primep candidate) candidate (try))))))