#lang typed/racket

(require "../base/base-random.rkt"
         "divisibility.rkt"
         "modular-arithmetic.rkt"
         "types.rkt"
         "small-primes.rkt")

(require/typed typed/racket
               [integer-sqrt/remainder (Natural -> (Values Natural Natural))])

(provide solve-chinese
         
         ; primes
         nth-prime
         random-prime
         next-prime untyped-next-prime
         next-primes
         prev-prime untyped-prev-prime
         prev-primes
         prime?
         odd-prime?
         factorize
         defactorize
         divisors
         prime-divisors
         prime-exponents
         prime-omega
         
         ; roots
         integer-root
         integer-root/remainder         
         
         ; Powers
         max-dividing-power
         perfect-power
         perfect-power?
         prime-power
         prime-power?
         odd-prime-power?
         as-power
         perfect-square

         ; number theoretic functions
         totient
         moebius-mu
         divisor-sum
         mangoldt-lambda
         )

;;;
;;; Configuration
;;;

(define prime-strong-pseudo-certainty 1/10000000)
(define prime-strong-pseudo-trials
  (integer-length (assert (/ 1 prime-strong-pseudo-certainty) integer?)))

(define *VERY-SMALL-PRIME-LIMIT* 1000)
; Determines the size of the pre-built table of very small primes
(define *SMALL-FACTORIZATION-LIMIT* *VERY-SMALL-PRIME-LIMIT*)
; Determines whether to use naive factorization or Pollards rho method.


;;;
;;; Powers
;;;

(: max-dividing-power : Integer Integer -> Natural)
; (max-dividing-power p n) = m  <=> p^m | n  and  p^(m+1) doesn't divide n
;   In Mathematica this one is called IntegerExponent
(define (max-dividing-power p n)
  (: find-start : Integer Integer -> Integer)
  (define (find-start p-to-e e)
    ;(display (list 'fs 'p-to-e p-to-e  'e e)) (newline)
    ; p-to-e divides n  and  p-to-e = p^e
    (let ([p-to-e2 (sqr p-to-e)])
      (cond [(= p-to-e2 n) (* 2 e)]
            [(> p-to-e2 n) (find-power p-to-e e)]
            [(divides? p-to-e2 n) (if (divides? p (quotient n p-to-e2))
                                      (find-start p-to-e2 (* 2 e))
                                      (* 2 e))]
            [else (find-power p-to-e e)])))
  (: find-power : Integer Integer -> Integer)
  (define (find-power p-to-e e)
    ;(display (list 'fp 'p-to-e p-to-e  'e e)) (newline)
    ; p-to-e <= n < (square p-to-e)
    (+ e (max-dividing-power-naive p (quotient n p-to-e))))
  (cond [(= p 1)              1]
        [(not (divides? p n)) 0]
        [else                 (assert (find-start p 1) natural?)]))

(: max-dividing-power-naive : Integer Integer -> Natural)
(define (max-dividing-power-naive p n)
  ; sames as max-dividing-power but using naive algorithm
  (: loop : Integer Integer -> Integer)
  (define (loop p-to-e e)
    (if (divides? p-to-e n)
        (loop (* p p-to-e) (+ e 1))
        (- e 1)))
  (if (= p 1)
      (error 'max-dividing-power "No maximal power of 1 exists")
      (assert (loop 1 0) natural?)))

; THEOREM (The Chinese Remainder Theorem)
;   Let n1,...,nk be positive integers with gcd(ni,nj)=1 whenever i<>j,
;   and let a1,...,ak be any integers. Then the solutions to
;     x=a1  mod n1,  ...,  x=ak  mod nk
;   has a single solution in {0,...,n-1}, where n=n1*...nk.

; Example : (solve-chinese '(2 3 2) '(3 5 7)) = 23

(: solve-chinese : (Listof Integer) (Listof Integer) -> Natural)
(define (solve-chinese as ns)
  (unless (andmap positive? ns)
    (raise-argument-error 'solve-chinese "(Listof Positive-Integer)" 1 as ns))
  ; the ns should be coprime
  (let* ([n  (apply * ns)]
         [cs (map (λ: ([ni : Integer]) (quotient n ni)) ns)]
         [ds (map modular-inverse cs ns)]
         [es (cast ds (make-predicate (Listof Integer)))])
    (cast (modulo (apply + (map * as cs es)) n) natural?)))

;;;
;;; PRIMES
;;;

(: odd-prime? : Integer -> Boolean)
(define (odd-prime? n)
  (and (odd? n) (prime? n)))

;;; PRIMALITY TESTS

; From Modern Computer Algebra by Joachim von sur Gathen and Jürgen Gerhard
; Strong pseudoprimality test
; The strong test returns one of:
;   'probably-prime                                        if n is a prime
;   'composite            (with at least probability 1/2)  if n is a composite non-Carmichael number
;   a proper divisor of n (with at least probability 1/2)  if n is a Carmichael number
; [MCA, p.509 - Algorithm 18.5]
(: prime-strong-pseudo-single? : Integer -> (U 'probably-prime 'composite Natural))
(define (prime-strong-pseudo-single? n)
  (cond
    [(n . <= . 0)  (raise-argument-error 'prime-strong-pseudo-single? "Positive-Integer" n)]
    [(n . >= . 4)
     (define a (random-integer 2 (- n 1)))
     (define g (gcd a n))
     (cond
       [(> g 1) g] ; factor found
       [else
        ; 3. write n-1 = 2^ν * m , m odd
        (let loop ([ν 0] [m (- n 1)])
          (cond 
            [(even? m) (loop (add1 ν) (quotient m 2))]
            [else ; 4. for i=1,...,ν do bi <- b_{i-1}^2 rem N
             (define b (modular-expt a m n))
             (cond 
               [(= b 1) 'probably-prime]
               [else    
                (let loop ([i 0] [b b] [b-old b])
                  (if (and (< i ν) (not (= b 1)))
                      (loop (add1 i)
                            (modulo (* b b) n)
                            b)
                      (if (= b 1)
                          (let ([g (gcd (+ b-old 1) n)])
                            (if (or (= g 1) (= g n))
                                'probably-prime
                                g))
                          'composite)))])]))])]
    [(= n 1)  'composite]
    [else  'probably-prime]))

(define-type Strong-Test-Result         (U 'very-probably-prime 'composite Natural))

(: prime-strong-pseudo/explanation : Natural -> Strong-Test-Result)
(define (prime-strong-pseudo/explanation n)
  ; run the strong test several times to improve probability
  (: loop : Integer (U Strong-Test-Result 'probably-prime) -> Strong-Test-Result)
  (define (loop trials result)
    (cond [(= trials 0)                 'very-probably-prime]
          [(eq? result 'probably-prime) (loop (sub1 trials) (prime-strong-pseudo-single? n))]
          [else                         result]))
  (loop prime-strong-pseudo-trials (prime-strong-pseudo-single? n)))

(: prime-strong-pseudo? : Natural -> Boolean)
(define (prime-strong-pseudo? n)
  (let ([explanation (prime-strong-pseudo/explanation n)])
    (or (eq? explanation 'very-probably-prime)
        (eq? explanation #t))))


(: prime? : Integer -> Boolean)
(define prime?
  (let ()
    ; Sieve of Eratosthenes
    ; https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
    ; TODO: Only store odd integers in this table
    (define N *VERY-SMALL-PRIME-LIMIT*)
    (define ps (make-vector (+ N 1) #t))
    (define ! vector-set!)
    (! ps 0 #f)
    (! ps 1 #f)
    (for ([n (in-range 2 (+ N 1))])
      (when (vector-ref ps n)
        (for ([m (in-range (+ n n) (+ N 1) n)])
          (! ps m #f))))
    (lambda (n)
      (let ([n (abs n)])
        (cond 
          [(< n N)
           (vector-ref ps n)]
          [(< n *SMALL-PRIME-LIMIT*)
           (small-prime? n)]
          [else
           (prime-strong-pseudo? n)])))))

(: next-prime : (case-> (Natural -> Natural)
                        (Integer -> Integer)))
(define (next-prime n)
  (cond
    [(negative? n) (- (prev-prime (abs n)))]
    [(= n 0) 2]
    [(= n 1) 2]
    [(= n 2) 3]
    [(even? n) (let ([n+1 (add1 n)])
                 (if (prime? n+1)
                     n+1
                     (next-prime n+1)))]
    [else      (let ([n+2 (+ n 2)])
                 (if (prime? n+2)
                     n+2
                     (next-prime n+2)))]))

(: untyped-next-prime : Integer -> Integer)
(define (untyped-next-prime z)
  (next-prime z))

(: untyped-prev-prime : Integer -> Integer)
(define (untyped-prev-prime z)
  (prev-prime z))


(: prev-prime : Integer -> Integer)
(define (prev-prime n)
  (cond
    [(negative? n) (- (next-prime (abs n)))]
    [(= n 3)   2]
    [(< n 3)   -2]
    [(even? n) (let ([n-1 (sub1 n)])
                 (if (prime? n-1)
                     n-1
                     (prev-prime n-1)))]
    [else      (let ([n-2 (- n 2)])
                 (if (prime? n-2)
                     n-2
                     (prev-prime n-2)))]))


(: next-primes : Integer Integer -> (Listof Integer))
(define (next-primes m primes-wanted)
  (cond
    [(primes-wanted . < . 0)  (raise-argument-error 'next-primes "Natural" 1 m primes-wanted)]
    [else
     (: loop : Integer Integer -> (Listof Integer))
     (define (loop n primes-wanted)
       (if (= primes-wanted 0)
           '()
           (let ([next (next-prime n)])
             (if next
                 (cons next (loop next (sub1 primes-wanted)))
                 '()))))
     (loop m primes-wanted)]))

(: prev-primes : Integer Integer -> (Listof Integer))
(define (prev-primes m primes-wanted)
  (cond
    [(primes-wanted . < . 0)  (raise-argument-error 'prev-primes "Natural" 1 m primes-wanted)]
    [else
     (: loop : Integer Integer -> (Listof Integer))
     (define (loop n primes-wanted)
       (if (= primes-wanted 0)
           '()
           (let ([prev (prev-prime n)])
             (if prev
                 (cons prev (loop prev (sub1 primes-wanted)))
                 '()))))
     (loop m primes-wanted)]))


(: nth-prime : Integer -> Natural)
(define (nth-prime n)
  (cond [(n . < . 0)  (raise-argument-error 'nth-prime "Natural" n)]
        [else
         (for/fold: ([p : Natural  2]) ([m (in-range n)])
           (next-prime p))]))

(: random-prime : Integer -> Natural)
(define (random-prime n)
  (when (<= n 2)
    (raise-argument-error 'random-prime "Natural > 2" n))
  (define p (random-natural n))
  (if (prime? p)
      p
      (random-prime n)))

;;;
;;; FACTORIZATION
;;;

(: factorize : Natural -> (Listof (List Natural Natural)))
(define (factorize n)
  (if (< n *SMALL-FACTORIZATION-LIMIT*)  ; NOTE: Do measurement of best cut
      (factorize-small n)
      (factorize-large n)))

(: defactorize : (Listof (List Natural Natural)) -> Natural)
(define (defactorize bes)
  (cond [(empty? bes) 1]
        [else (define be (first bes))
              (* (expt (first be) (second be))
                 (defactorize (rest bes)))]))

(: factorize-small : Natural -> (Listof (List Natural Natural)))
(define (factorize-small n)
  ; fast for small n, but works correctly for large n too
  (small-prime-factors-over n 2))

(: small-prime-factors-over : Natural Natural -> (Listof (List Natural Natural)))
; Factor a number n without prime factors below the prime p.
(define (small-prime-factors-over n p) ; p prime
  (cond
    [(<= p 0)        (raise-argument-error 'small-prime-factors-over "Natural" p)]
    [(< n p)         '()]
    [(= n p)         (list (list p 1))]
    [(prime? n)      (list (list n 1))]
    [(divides? p n)  (let ([m (max-dividing-power p n)])
                       (cons (list p m)
                             (small-prime-factors-over 
                              (quotient n (expt p m))
                              (next-prime p))))]
    [else            (small-prime-factors-over n (next-prime p))]))


;;; ALGORITHM 19.8  Pollard's rho method
; INPUT   n>=3 neither a prime nor a perfect power
; OUTPUT  Either a proper divisor of n or #f
(: pollard : Natural -> (U Natural False))
(define (pollard n)
  (let ([x0 (random-natural n)])
    (do ([xi x0 (remainder (+ (* xi xi) 1) n)]
         [yi x0 (remainder (+ (sqr (+ (* yi yi) 1)) 1) n)]
         [i  0  (add1 i)]
         [g  1  (gcd (- xi yi) n)])
      [(or (< 1 g n) (> i (sqrt n)))
       (if (< 1 g n)
           (cast g natural?)
           #f)])))

(: pollard-factorize : Natural -> (Listof (List Natural Natural)))
(define (pollard-factorize n)
  (if (< n *SMALL-FACTORIZATION-LIMIT*)
      (factorize-small n)
      (cond
        [(= n 1)        '()]
        [(prime? n)     `((, n 1))]
        [(even? n)      `((2 1) ,@(pollard-factorize (quotient n 2)))]
        [(divides? 3 n) `((3 1) ,@(pollard-factorize (quotient n 3)))]
        [(simple-perfect-power n)
         => (λ: ([base-and-exp : (List Natural Natural)]) 
              (cond
                [(prime? (car base-and-exp)) (list base-and-exp)]
                [else (map (λ: ([b-and-e : (List Natural Natural)])
                             (list (car b-and-e) 
                                   (* (cadr base-and-exp) (cadr b-and-e))))
                           (pollard-factorize (car base-and-exp)))]))]
        [else                 
         (let loop ([divisor (pollard n)])
           (if divisor
               (append (pollard-factorize divisor)
                       (pollard-factorize (quotient n divisor)))
               (loop (pollard n))))])))

(: factorize-large : Natural -> (Listof (List Natural Natural)))
(define (factorize-large n)
  (combine-same-base
   (sort (pollard-factorize n) base-and-exponent<?)))

(: base-and-exponent<? ((U Natural (List Natural Natural)) (U Natural (List Natural Natural))
                                                           -> Boolean))
(define (base-and-exponent<? x y)
  (let ([id-or-first 
         (λ: ([x : (U Integer (List Integer Integer))])
           (if (number? x) x (first x)))])
    (<= (id-or-first x) (id-or-first y))))

(: combine-same-base : (Listof (List Natural Natural)) -> (Listof (List Natural Natural)))
(define (combine-same-base list-of-base-and-exponents)
  ; list-of-base-and-exponents must be sorted
  (let ([l list-of-base-and-exponents])
    (cond
      [(null? l)        '()]
      [(null? (cdr l))  l]
      [else             
       (define b1 (first  (first l)))
       (define e1 (second (first l)))
       (define b2 (first  (second l)))
       (define e2 (second (second l)))
       (define more (cddr l))
       (if (= b1 b2)
           (combine-same-base (cons (list b1 (+ e1 e2))
                                    (cdr (cdr list-of-base-and-exponents))))
           (cons (car list-of-base-and-exponents)
                 (combine-same-base (cdr list-of-base-and-exponents))))])))


; find-tail pred clist -> pair or false
; Return the first pair of clist whose car satisfies pred. If no pair does, return false.
(: find-tail : (Integer -> Boolean) (Listof Integer) -> (U False (Listof Integer)))
(define (find-tail pred xs)
  (cond [(empty? xs) #f]
        [(pred (car xs)) xs]
        [else (find-tail pred (cdr xs))]))


;;;
;;; Powers
;;;

(: as-power : Exact-Positive-Integer -> (Values Natural Natural))
;   Write a>0 as b^r with r maximal. Return b and r.
(define (as-power a)    
  (let ([r (apply gcd ((inst map Natural (List Natural Natural)) second (factorize a)))])
    (values (integer-root a r) r)))


(: prime-power : Natural -> (U (List Natural Natural) False))
;   if n is a prime power, return list of prime and exponent in question,
;   otherwise return #f
(define (prime-power n)
  (let ([factorization (prime-divisors/exponents n)])
    (if (= (length factorization) 1)
        (first (prime-divisors/exponents n))
        #f)))

(: prime-power? : Natural -> Boolean)
;   Is n of the form p^m, with p is prime?
(define (prime-power? n)
  (and (prime-power n) #t))

(: odd-prime-power? : Natural -> Boolean)
(define (odd-prime-power? n)
  (let ([p/e (prime-power n)])
    (and p/e
         (odd? (first p/e)))))

(: perfect-power? : Natural -> Boolean)
(define (perfect-power? a)
  (and (not (zero? a))
       (let-values ([(base n) (as-power a)])
         (and (> n 1) (> a 1)))))

(: simple-perfect-power : Natural -> (U (List Natural Natural) False))
(define (simple-perfect-power a)
  ; simple-perfect-power is used by pollard-fatorize
  (and (not (zero? a))
       (let-values ([(base n) (simple-as-power a)])
         (if (and (> n 1) (> a 1))
             (list base n)
             #f))))

(: perfect-power : Natural -> (U (List Natural Natural) False))
;   if a = b^n with b>1 and n>1
(define (perfect-power a)
  (and (not (zero? a))
       (let-values ([(base n) (as-power a)])
         (if (and (> n 1) (> a 1))
             (list base n)
             #f))))

(: perfect-square : Natural -> (U Natural False))
(define (perfect-square n)
  (let ([sqrt-n (integer-sqrt n)])
    (if (= (* sqrt-n sqrt-n) n)
        sqrt-n
        #f)))

(: powers-of : Natural Natural -> (Listof Natural))
;   returns a list of numbers: a^0, ..., a^n
(define (powers-of a n)
  (let: loop : (Listof Natural)
    ([i   : Natural 0] 
     [a^i : Natural 1])
    (if (<= i n)
        (cons a^i (loop (+ i 1) (* a^i a)))
        '())))

(define prime-divisors/exponents factorize)

(: prime-divisors : Natural -> (Listof Natural))
;   return list of primes in a factorization of n
(define (prime-divisors n)
  (map (inst car Natural (Listof Natural))
       (prime-divisors/exponents n)))

(: prime-exponents : Natural -> (Listof Natural))
;   return list of exponents in a factorization of n
(define (prime-exponents n)
  (map (inst cadr Natural Natural (Listof Natural)) 
       (prime-divisors/exponents n)))

(: prime-omega : Natural -> Natural)
; http://reference.wolfram.com/mathematica/ref/PrimeOmega.html
(define (prime-omega n)
  (for/fold: ([sum : Natural 0]) ([e (in-list (prime-exponents n))])
    (+ sum e)))


(: integer-root/remainder : Natural Natural -> (Values Natural Natural))
(define (integer-root/remainder a n)
  (let ([i (integer-root a n)])
    (values i (assert (- a (expt i n)) natural?))))

(: integer-root : Natural Natural -> Natural)
(define (integer-root x y)
    ; y'th root of x
    (cond 
      [(eq? x 0) 0]
      [(eq? x 1) 1]
      [(eq? y 1) x]
      [(eq? y 2) (integer-sqrt x)]
      [(not (integer? y))
       (error 'integer-root "internal error (used to return 1 here - why?) remove after testing")]
      [else
       (define length (integer-length x))
       ;; (expt 2 (- length l 1)) <= x < (expt 2 length)
       (assert
        (cond [(<= length y) 1]
              ;; result is >= 2
              [(<= length (* 2 y))
               ;; result is < 4
               (if (< x (expt 3 y)) 2 3)]
              [(even? y) (integer-root (integer-sqrt x) (quotient y 2))]
              [else
               (let* ([length/y/2 ;; length/y/2 >= 1 because (< (* 2 y) length)
                       (quotient (quotient (- length 1) y) 2)])
                 (let ([init-g
                        (let* ([top-bits          (arithmetic-shift x (- (* length/y/2 y)))]
                               [nth-root-top-bits (integer-root top-bits y)])
                          (arithmetic-shift (+ nth-root-top-bits 1) length/y/2))])
                   (let: loop : Integer ([g : Integer init-g])
                     (let* ([a (expt g (assert (- y 1) natural?))]
                            [b (* a y)]
                            [c (* a (- y 1))]
                            [d (quotient (+ x (* g c)) b)])
                       (let ([diff (- d g)])
                         (cond [(not (negative? diff))
                                g]
                               [(< diff -1)
                                (loop d)]
                               [else
                                ;; once the difference is one, it's more
                                ;; efficient to just decrement until g^y <= x
                                (let loop ((g d))
                                  (if (not (< x (expt g y)))
                                      g
                                      (loop (- g 1))))]))))))])
        natural?)]))


(: simple-as-power : Exact-Positive-Integer -> (Values Natural Natural))
;    For a>0 write it as a = b^r where r maximal
;    return (values b r)
(define (simple-as-power a)
  ; (displayln (list 'simple-as-power a))
  ; Note: The simple version is used by pollard-factorize
  (let: loop : (Values Natural Natural)
    ([n : Natural (integer-length a)])
    (let-values ([(root rem) (integer-root/remainder a (add1 n))])
      (if (zero? rem)
          (values root (assert (add1 n) natural?))
          (if (positive? n)
              (loop (sub1 n))
              (error 'simple-as-power "internal error"))))))

(: prime-power? : Natural -> Boolean)

;;;
;;; DIVISORS
;;;

(: divisors : Integer -> (Listof Natural))
;   return the positive divisors of n
(define (divisors n)
  (cond [(zero? n) '()]
        [else (define n+ (if (positive? n) n (- n)))
              (sort (factorization->divisors (factorize n+)) <)]))

(: factorization->divisors : (Listof (List Natural Natural)) -> (Listof Natural))
(define (factorization->divisors f)
  (cond
    [(null? f) '(1)]
    [else (let ([p (first (first f))]
                [n (second (first f))]
                [g (rest f)])
            ; f = p^n * g
            (let ([divisors-of-g (factorization->divisors g)])
              (apply append
                     ((inst map (Listof Natural) Natural)
                      (λ: ([p^i : Natural]) (map (λ: ([d : Natural]) (* p^i d)) divisors-of-g))
                      (powers-of p n)))))]))

;;;
;;; Number theoretic functions
;;;

; DEFINITION (Euler's phi function  aka  totient)
;  phi(n) is the number of integers a=1,2,... such that gcd(a,n)=1

; THEOREM
;   If m and n are coprime then
;     phi(mn) = phi(m) phi(n) 

; THEOREM (Euler's phi function)
;  If the prime power factorization of p is
;           e1     ek
;     n = p1 ... pk     , where pi is prime and ei>0
;  then
;                   k          1
;   phi(n) = n * product (1 - ---- )
;                  i=1         pi

(: totient : Natural -> Natural)
(define (totient n)
  (let ((ps (prime-divisors n)))
    (assert (* (quotient n (apply * ps))
               (apply * (map (λ: ([p : Natural]) (sub1 p)) ps)))
            natural?)))

(: every : (All (A) (A -> Boolean) (Listof A) -> Boolean))
(define (every pred xs)
  (or (empty? xs)
      (and (pred (car xs))
           (every pred (cdr xs)))))


; moebius-mu : natural -> {-1,0-1}
;   mu(n) =  1  if n is a product of an even number of primes
;         = -1  if n is a product of an odd number of primes
;         =  0  if n has a multiple prime factor
(: moebius-mu : Natural -> (U -1 0 1))
(define (moebius-mu n)
  (: one? : Integer -> Boolean)
  (define (one? x) (= x 1))
  (define f         (factorize n))
  (define exponents ((inst map Natural (List Natural Natural)) second f))
  (cond 
    [(every one? exponents)
     (define primes ((inst map Natural (List Natural Natural)) first f))
     (if (even? (length primes))
         1 -1)]
    [else 0]))


(: divisor-sum : (case-> (Natural -> Natural) (Natural Natural -> Natural)))                   
(define divisor-sum 
  ; returns the sum of the kth power of all divisors of n
  (let ()
    (case-lambda 
      [(n)   (divisor-sum n 1)]
      [(n k) (let* ([f  (factorize n)]
                    [ps ((inst map Natural (List Natural Natural)) first f)]
                    [es ((inst map Natural (List Natural Natural)) second f)])
               (: divisor-sum0 : Any Natural -> Natural)
               (define (divisor-sum0 p e) (+ e 1))
               (: divisor-sum1 : Natural Natural -> Natural)
               (define (divisor-sum1 p e)
                 (let: loop : Natural 
                   ([sum    : Natural 1]
                    [n      : Natural 0]
                    [p-to-n : Natural 1])
                   (cond [(= n e) sum]
                         [else (let ([t (* p p-to-n)])
                                 (loop (+ t sum) (+ n 1) t))])))
               (: divisor-sumk : Natural Natural -> Natural)
               (define (divisor-sumk p e)
                 (let ([p-to-k (expt p k)])
                   (let: loop : Natural
                     ([sum     : Natural 1]
                      [n       : Natural 0]
                      [p-to-kn : Natural 1])
                     (cond [(= n e) sum]
                           [else (let ([t (* p-to-k p-to-kn)])
                                   (loop (+ t sum) (+ n 1) t))]))))
               (cast
                (apply * (map (cond [(= k 0) divisor-sum0]
                                    [(= k 1) divisor-sum1]
                                    [else    divisor-sumk])
                              ps es))
                natural?))])))

(: mangoldt-lambda : Integer -> Real)                   
(define (mangoldt-lambda n)
  (cond 
    [(<= n 0) (raise-argument-error 'mangoldt-lambda "Natural" n)]
    [else (define am (prime-power n))
          (cond
            [(cons? am) (log (car am))]
            [else 0])]))

; These tests are for un-exported functions.
#;(begin
    (require typed/rackunit)
    
    (check-equal? (max-dividing-power-naive 3 27) 3)
    (check-equal? (max-dividing-power-naive 3 (* 27 2)) 3)
    
    (check-true   (<= 4 (random-integer 4 5) 4))
    
    (check-false (prime-fermat? 0))
    (check-false (prime-fermat? 1))
    (check-false (prime-fermat? 4))
    (check-false (prime-fermat? 6))
    (check-false (prime-fermat? 8))
    
    (check-equal? (prime-fermat? 2)   #t)
    (check-equal? (prime-fermat? 3)   #t)
    (check-equal? (prime-fermat? 5)   'possibly-prime)
    (check-equal? (prime-fermat? 7)   'possibly-prime)
    (check-equal? (prime-fermat? 11)  'possibly-prime)
    (check-true   (member? (prime-fermat? 561) '(#f possibly-prime))) ; Carmichael number
    
    (check-equal? (prime-strong-pseudo-single? 4) 2)
    (check-true  (member? (prime-strong-pseudo-single? 6) '(2 3)))
    (check-true  (member? (prime-strong-pseudo-single? 8) '(2 4 composite)))
    
    (check-equal? (prime-strong-pseudo-single? 5)   'probably-prime)
    (check-equal? (prime-strong-pseudo-single? 7)   'probably-prime)
    (check-equal? (prime-strong-pseudo-single? 11)  'probably-prime)
    ;; Carmichael number:
    (check-true   (member? (prime-strong-pseudo-single? 561) (cons 'probably-prime (divisors 561))))
    )
