Quadratic Sieve With Large Primes

April 2, 2013

The previous version of the quadratic sieve used three functions: factor-base calculated the factors in the factor base, smooth determined if a number was smooth with respect to the factor base and returned its factors if it was, and qs was the main calling function that organized the computation. All three of those functions will change. In particular, qs will become more of a driver function with less in-line calculation, and we introduce two new functions: sieve performs the actual sieving and match combines partials into full relations. The supporting functions like jacobi and mod-sqrt are unchanged. The linear algebra changes slightly; make-expo-vector needs to ignore large prime factors, and the calculation of the square root is improved.

We begin with factor-base, combining the search for quadratic residues, calculation of the starting offsets, and the calculation of the approximate logarithm into a single loop. Quadratic residues are calculated as they were previously. Logarithms are simple if you keep your wits about you: any factor base prime between 2x−1/2 and 2x+1/2 will have an approximate logarithm of x. That means we can keep track of the next place where the logarithm changes, and recalculate the next change point each time we breach the last change point. The error factor e is set at double the maximum logarithm; this generally gives us a larger range of possible smooth numbers than the original version of the program, but still keeps us in a relatively small range of potential smooth numbers.

(define (factor-base n f) ; => (values fb ts ls e)
  (let loop ((ps (cdr (primes f))) (fb (list 2)) (ts (list)) (ls (list)) (x 2) (limit 5))
    (when (and (pair? ps) (< limit (car ps)))
      (set! x (+ x 1)) (set! limit (isqrt (expt 2 (+ x x 1)))))
    (cond ((null? ps) (values (reverse fb) (reverse ts) (reverse ls) (max 5 x)))
          ((= (jacobi n (car ps)) 1)
            (loop (cdr ps) (cons (car ps) fb)
                  (cons (msqrt n (car ps)) ts) (cons x ls) x limit))
          (else (loop (cdr ps) fb ts ls x limit)))))

Next is smooth, which has to change to recognize large primes. Fortunately, the change is small; if we ever get to the point where f × f > n, then n is prime and we return the smooth relation, whether or not n > f.

(define (smooth n fb) ; list of smooth factors of n in descending order, or null
  (let loop ((n (abs n)) (fb fb) (fs (if (negative? n) (list -1) (list))))
    (cond ((null? fb) (list))
          ((< n (* (car fb) (car fb))) (cons n fs))
          ((zero? (modulo n (car fb)))
            (loop (/ n (car fb)) fb (cons (car fb) fs)))
          (else (loop n (cdr fb) fs)))))

The new function sieve implements the segmented sieve; we extract that code to a separate function because it is rather more complicated than we want to have in the driver. Function sieve takes as inputs the fb, ts, ls and e computed by factor-base, as well as n and m, and returns two values: a list of relations rels and a list of partial relations parts.

(define (sieve n fb ts ls e f m) ; => (values rels parts)
  ; a relation, whether full or partial, has x in its car and a list
  ; of smooth factors of g(x)=x^2-n, in descending order, in its cdr;
  ; a large prime, if it exists, is at the cadr of the relation
  (let* ((sqrt-n (isqrt n)) (b (max (- sqrt-n m) 2)) (rels (list)) (parts (list))
         (lo b) (hi (+ b m m)) (delta 100000) (sieve (make-vector delta)))
    (do ((lo lo (+ lo delta))) ((< hi lo))
      (do ((i 0 (+ i 1))) ((= delta i)) (vector-set! sieve i (+ e e)))
      (do ((fb (cdr fb) (cdr fb)) (ts ts (cdr ts)) (ls ls (cdr ls))) ((null? fb))
        (do ((i (modulo (- (car ts) lo) (car fb)) (+ i (car fb)))) ((<= delta i))
          (vector-set! sieve i (+ (vector-ref sieve i) (car ls))))
        (do ((i (modulo (- (- (car ts)) lo) (car fb)) (+ i (car fb)))) ((<= delta i))
          (vector-set! sieve i (+ (vector-ref sieve i) (car ls)))))
      (do ((i 0 (+ i 1))) ((= delta i))
        (let* ((x (+ i lo)) (g (- (* x x) n)))
          (if (< (ilog 2 g) (vector-ref sieve i))
            (let* ((ys (smooth g fb)) (rel (cons x ys)))
              (if (pair? ys) (if (<= (car ys) f)
                                 (set! rels (cons rel rels))
                                 (set! parts (cons rel parts)))))))))

The other new function match takes the list of partial relations and returns as many full relations as it can find; it first sorts on the large primes, then scans, writing a full relation (including the two copies of the large prime) whenever it finds a large prime the same as its predecessor.

(define (match parts)
  (define (lt? a b) (< (cadr a) (cadr b)))
  (let loop ((parts (sort lt? parts)) (prev (list 0 0)) (zs (list)))
    (cond ((null? parts) zs)
          ((= (cadar parts) (cadr prev))
            (loop (cdr parts) prev
                  (cons (cons (* (caar parts) (car prev))
                              (merge > (cdar parts) (cdr prev))) zs)))
          (else (loop (cdr parts) (car parts) zs)))))

The last changed function is qs, which connects the rest of the functions as needed; it first computes the factor base, then sieves, computes matches, and finally solves.

(define (qs n f m)
  (call-with-values
    (lambda () (factor-base n f))
    (lambda (fb ts ls e)
      (when verbose? (display "Factor base of ")
        (display (length fb)) (display " primes.") (newline))
      (call-with-values
        (lambda () (sieve n fb ts ls e f m))
        (lambda (rels parts)
          (let ((matches (match parts)))
            (when verbose? (display "Found ") (display (length rels))
              (display " smooth relations,") (newline)
              (display (length parts)) (display " partial relations,")
              (newline) (display "and ") (display (length matches))
              (display " matches.") (newline))
            (solve n f fb (append rels matches))))))))

Here are some examples.

> (qs 87463 30 30)
Factor base of 6 primes.
Found 8 smooth relations,
101 partial relations,
and 47 matches.
587
> (qs 13290059 150 300)
Factor base of 18 primes.
Found 125 smooth relations,
1695 partial relations,
and 1071 matches.
4261
> (qs 294729242679158229936006281 2000 3000000)
Factor base of 149 primes.
Found 160 smooth relations,
6118 partial relations,
and 695 matches.
2971215073

We used the same examples as last time, including the same f and m. If you compare, you will see that with the same effort we found more relations; there are more smooth relations because of the change in calculating the error adjustment, and the matches are completely new. By reducing f or m or both, we could have found sufficient relations with less effort. And the effect grows as n gets larger. The improvements are well worth the effort we made and the additional complexity they bring to the code.

You can run the complete program at http://programmingpraxis.codepad.org/ohwDTFhz. We’re not finished with the quadratic sieve just yet, so keep watching for additional improvements in the future.

Pages: 1 2

Leave a comment