(* ========================================================================= *)
(* Quadratic reciprocity.                                                    *)
(* ========================================================================= *)
needs "Library/prime.ml";;
needs "Library/pocklington.ml";;
prioritize_num();;
(* ------------------------------------------------------------------------- *)
(* Misc. lemmas.                                                             *)
(* ------------------------------------------------------------------------- *)
let EVEN_DIV = prove
 (`!n. 
EVEN n <=> n = 2 * (n DIV 2)`,
  GEN_TAC THEN REWRITE_TAC[
EVEN_MOD] THEN
  MP_TAC(SPEC `n:num` (MATCH_MP 
DIVISION (ARITH_RULE `~(2 = 0)`))) THEN
  ARITH_TAC);;
 
let CONG_COND_LEMMA = prove
 (`!p x y. 3 <= p /\
           ((if x then 1 else p - 1) == (if y then 1 else p - 1)) (mod p)
           ==> (x <=> y)`,
 
let IS_QUADRATIC_RESIDUE_PAIR = prove
 (`!a p. prime p /\ coprime(a,p)
         ==> (a 
is_quadratic_residue (mod p) <=>
                 ?x y. 0 < x /\ x < p /\ 0 < y /\ y < p /\ x + y = p /\
                       (x 
EXP 2 == a) (mod p) /\ (y 
EXP 2 == a) (mod p) /\
                       !z. 0 < z /\ z < p /\ (z 
EXP 2 == a) (mod p)
                           ==> z = x \/ z = y)`,
  SIMP_TAC[
IS_QUADRATIC_RESIDUE_COMMON] THEN REPEAT STRIP_TAC THEN
  EQ_TAC THENL [ALL_TAC; MESON_TAC[]] THEN
  DISCH_THEN(X_CHOOSE_THEN `x:num` STRIP_ASSUME_TAC) THEN
  MAP_EVERY EXISTS_TAC [`x:num`; `p - x:num`] THEN
  ASM_SIMP_TAC[ARITH_RULE
   `0 < x /\ x < p ==> 0 < p - x /\ p - x < p /\ x + (p - x) = p`] THEN
  REPEAT STRIP_TAC THEN
  FIRST_X_ASSUM(MP_TAC o MATCH_MP 
QUADRATIC_RESIDUE_PAIR) THENL
   [DISCH_THEN(MP_TAC o SPECL [`x:num`; `p - x:num`]) THEN
    ASM_SIMP_TAC[ARITH_RULE `x < p ==> x + (p - x) = p`; 
DIVIDES_REFL] THEN
    ASM_MESON_TAC[
CONG_TRANS; 
CONG_SYM];
    DISCH_THEN(MP_TAC o SPECL [`x:num`; `z:num`]) THEN
    SUBGOAL_THEN `(x 
EXP 2 == z 
EXP 2) (mod p)` (fun th -> SIMP_TAC[th]) THENL
     [ASM_MESON_TAC[
CONG_TRANS; 
CONG_SYM]; ALL_TAC] THEN
    DISCH_THEN(DISJ_CASES_THEN (MP_TAC o MATCH_MP 
DIVIDES_CASES)) THEN
    REWRITE_TAC[
ADD_EQ_0; 
DIST_EQ_0] THEN REWRITE_TAC[dist] THEN
    ASM_ARITH_TAC]);;
 
let NPRODUCT_PAIRUP_INDUCT = prove
 (`!f r n s k. s 
HAS_SIZE (2 * n) /\
               (!x:A. x 
IN s ==> ?!y. y 
IN s /\ ~(y = x) /\
                                      (f(x) * f(y) == k) (mod r))
               ==> (nproduct s f == k 
EXP n) (mod r)`,
  GEN_TAC THEN GEN_TAC THEN
  MATCH_MP_TAC 
num_WF THEN X_GEN_TAC `n:num` THEN DISCH_TAC THEN
  X_GEN_TAC `s:A->bool` THEN GEN_TAC THEN ASM_CASES_TAC `n = 0` THENL
   [ASM_SIMP_TAC[
MULT_CLAUSES; 
HAS_SIZE_0; 
NPRODUCT_CLAUSES; 
EXP; 
CONG_REFL];
    ALL_TAC] THEN
  ASM_CASES_TAC `s:A->bool = {}` THENL
   [ASM_MESON_TAC[
HAS_SIZE_0; ARITH_RULE `2 * n = 0 <=> n = 0`; 
HAS_SIZE];
    ALL_TAC] THEN
  STRIP_TAC THEN FIRST_X_ASSUM(X_CHOOSE_TAC `a:A` o
    GEN_REWRITE_RULE I [GSYM 
MEMBER_NOT_EMPTY]) THEN
  FIRST_X_ASSUM(MP_TAC o SPEC `n - 1`) THEN
  ASM_SIMP_TAC[ARITH_RULE `~(n = 0) ==> n - 1 < n`] THEN
  FIRST_ASSUM(MP_TAC o SPEC `a:A`) THEN REWRITE_TAC[ASSUME `(a:A) 
IN s`] THEN
  REWRITE_TAC[
EXISTS_UNIQUE] THEN
  DISCH_THEN(X_CHOOSE_THEN `b:A` STRIP_ASSUME_TAC) THEN
  DISCH_THEN(MP_TAC o SPECL [`(s 
DELETE a) 
DELETE (b:A)`; `k:num`]) THEN
  SUBGOAL_THEN `s = (a:A) 
INSERT (b 
INSERT (s 
DELETE a 
DELETE b))`
   (ASSUME_TAC o SYM) THENL [ASM SET_TAC[]; ALL_TAC] THEN ANTS_TAC THENL
   [CONJ_TAC THENL
     [UNDISCH_TAC `(s:A->bool) 
HAS_SIZE 2 * n` THEN
      FIRST_X_ASSUM(fun th -> GEN_REWRITE_TAC (LAND_CONV o LAND_CONV)
        [SYM th]) THEN
      SIMP_TAC[
HAS_SIZE; 
FINITE_INSERT; 
CARD_CLAUSES; 
FINITE_DELETE;
               
IMP_CONJ; 
IN_DELETE; 
IN_INSERT] THEN
      ASM_REWRITE_TAC[] THEN ARITH_TAC;
      ALL_TAC] THEN
    X_GEN_TAC `x:A` THEN ASM_REWRITE_TAC[
IN_DELETE] THEN STRIP_TAC THEN
    FIRST_ASSUM(MP_TAC o C MATCH_MP (ASSUME `(x:A) 
IN s`)) THEN
    REWRITE_TAC[
EXISTS_UNIQUE] THEN MATCH_MP_TAC 
MONO_EXISTS THEN
    X_GEN_TAC `y:A` THEN STRIP_TAC THEN CONJ_TAC THENL
     [ALL_TAC; ASM_MESON_TAC[]] THEN
    ASM_REWRITE_TAC[] THEN CONJ_TAC THEN DISCH_THEN SUBST_ALL_TAC THENL
     [ASM_MESON_TAC[
MULT_SYM]; ALL_TAC] THEN
    FIRST_X_ASSUM(MP_TAC o C MATCH_MP (ASSUME `(b:A) 
IN s`)) THEN
    REWRITE_TAC[
EXISTS_UNIQUE_THM] THEN
    DISCH_THEN(MP_TAC o SPECL [`a:A`; `x:A`] o CONJUNCT2) THEN
    ASM_MESON_TAC[
MULT_SYM];
    ALL_TAC] THEN
  DISCH_TAC THEN EXPAND_TAC "s" THEN
  FIRST_X_ASSUM(MP_TAC o CONJUNCT1 o REWRITE_RULE[
HAS_SIZE]) THEN
  SIMP_TAC[
NPRODUCT_CLAUSES; 
FINITE_INSERT; 
FINITE_DELETE] THEN
  REWRITE_TAC[
IN_INSERT; 
IN_DELETE; 
MULT_CLAUSES] THEN
  FIRST_ASSUM(SUBST1_TAC o MATCH_MP
   (ARITH_RULE `~(n = 0) ==> n = SUC(n - 1)`)) THEN
  ASM_REWRITE_TAC[
MULT_ASSOC; 
EXP] THEN DISCH_TAC THEN
  MATCH_MP_TAC 
CONG_MULT THEN ASM_REWRITE_TAC[]);;
 
let WILSON = prove
 (`!p. ~(p = 1) ==> (prime p <=> (
FACT(p - 1) == p - 1) (mod p))`,
  X_GEN_TAC `n:num` THEN DISCH_TAC THEN EQ_TAC THEN SIMP_TAC[
WILSON_IMP] THEN
  FIRST_X_ASSUM(MP_TAC o MATCH_MP 
PRIME_FACTOR) THEN
  DISCH_THEN(X_CHOOSE_THEN `p:num` STRIP_ASSUME_TAC) THEN
  FIRST_ASSUM(MP_TAC o MATCH_MP 
DIVIDES_LE) THEN
  ASM_CASES_TAC `n = 0` THEN ASM_REWRITE_TAC[] THENL
   [ASM_REWRITE_TAC[
CONG_MOD_0] THEN CONV_TAC NUM_REDUCE_CONV; ALL_TAC] THEN
  REWRITE_TAC[
LE_LT] THEN ASM_CASES_TAC `n:num = p` THEN ASM_REWRITE_TAC[] THEN
  DISCH_THEN(MP_TAC o MATCH_MP (ARITH_RULE `x < y ==> x <= y - 1`)) THEN
  ASM_SIMP_TAC[GSYM 
DIVIDES_FACT_PRIME] THEN
  DISCH_THEN(fun th -> DISCH_TAC THEN MP_TAC th) THEN
  SUBGOAL_THEN `p divides 
FACT(n - 1) <=> p divides (n - 1)` SUBST1_TAC THENL
   [MATCH_MP_TAC 
CONG_DIVIDES THEN
    MATCH_MP_TAC 
CONG_MOD_MULT THEN EXISTS_TAC `n:num` THEN
    ASM_REWRITE_TAC[];
    ALL_TAC] THEN
  DISCH_TAC THEN SUBGOAL_THEN `p divides 1` MP_TAC THENL
   [MATCH_MP_TAC 
DIVIDES_ADD_REVR THEN EXISTS_TAC `n - 1` THEN
    ASM_SIMP_TAC[ARITH_RULE `~(n = 0) ==> n - 1 + 1 = n`];
    REWRITE_TAC[
DIVIDES_ONE] THEN ASM_MESON_TAC[
PRIME_1]]);;
 
let GAUSS_LEMMA_1 = prove
 (`prime p /\ coprime(a,p) /\ 2 * r + 1 = p
   ==> nproduct(1..r) (\x. let b = (a * x) MOD p in
                           if b <= r then b else p - b) =
       nproduct(1..r) (\x. x)`,
  REPEAT STRIP_TAC THEN FIRST_ASSUM(ASSUME_TAC o MATCH_MP 
PRIME_IMP_NZ) THEN
  GEN_REWRITE_TAC (LAND_CONV o RAND_CONV) [SYM(CONJUNCT1(SPEC_ALL 
I_O_ID))] THEN
  REWRITE_TAC[
I_DEF] THEN MATCH_MP_TAC 
NPRODUCT_INJECTION THEN
  REWRITE_TAC[
FINITE_NUMSEG] THEN
  ABBREV_TAC `f = \x. let b = (a * x) MOD p in
                      if b <= r then b else p - b` THEN
  MATCH_MP_TAC(TAUT `a /\ (a ==> b) ==> a /\ b`) THEN CONJ_TAC THENL
   [GEN_TAC THEN EXPAND_TAC "f" THEN REWRITE_TAC[
IN_NUMSEG] THEN
    LET_TAC THEN REWRITE_TAC[
LET_DEF; 
LET_END_DEF] THEN REPEAT STRIP_TAC THENL
     [ALL_TAC; EXPAND_TAC "p" THEN ARITH_TAC] THEN
    REWRITE_TAC[ARITH_RULE `1 <= x <=> ~(x = 0)`] THEN COND_CASES_TAC THENL
     [ALL_TAC; ASM_MESON_TAC[
DIVISION; 
NOT_LE; 
SUB_EQ_0; 
PRIME_0]] THEN
    EXPAND_TAC "b" THEN ASM_SIMP_TAC[GSYM 
DIVIDES_MOD; 
PRIME_IMP_NZ] THEN
    ASM_SIMP_TAC[
PRIME_DIVPROD_EQ] THEN STRIP_TAC THENL
     [ASM_MESON_TAC[coprime; 
DIVIDES_REFL; 
PRIME_1];
      ASM_MESON_TAC[
DIVIDES_LE; ARITH_RULE `~(1 <= 0)`;
                    ARITH_RULE `~(2 * r + 1 <= i /\ i <= r)`]];
    REWRITE_TAC[
LET_DEF; 
LET_END_DEF] THEN DISCH_TAC] THEN
  MAP_EVERY X_GEN_TAC [`i:num`; `j:num`] THEN REWRITE_TAC[
IN_NUMSEG] THEN
  REPEAT STRIP_TAC THEN MATCH_MP_TAC 
CONG_IMP_EQ THEN EXISTS_TAC `p:num` THEN
  REPEAT(CONJ_TAC THENL
   [ASM_MESON_TAC[ARITH_RULE `i <= r ==> i < 2 * r + 1`] ; ALL_TAC]) THEN
  MATCH_MP_TAC 
CONG_MULT_LCANCEL THEN EXISTS_TAC `a:num` THEN
  FIRST_X_ASSUM(MP_TAC o MATCH_MP (ARITH_RULE
   `(if a then x else p - x) = (if b then y else p - y) ==> x < p /\ y < p
    ==> x = y \/ x + y = p`)) THEN ASM_SIMP_TAC[
DIVISION] THEN
  DISCH_THEN(DISJ_CASES_THEN MP_TAC) THENL [ASM_MESON_TAC[
CONG]; ALL_TAC] THEN
  DISCH_THEN(MP_TAC o C AP_THM `p:num` o AP_TERM `(MOD)`) THEN
  ASM_SIMP_TAC[
MOD_ADD_MOD] THEN ASM_SIMP_TAC[GSYM 
CONG] THEN
  DISCH_THEN(MP_TAC o MATCH_MP 
CONG_DIVIDES) THEN
  ASM_SIMP_TAC[GSYM 
LEFT_ADD_DISTRIB; 
PRIME_DIVPROD_EQ; 
DIVIDES_REFL] THEN
  STRIP_TAC THENL
   [ASM_MESON_TAC[coprime; 
DIVIDES_REFL; 
PRIME_1]; ALL_TAC] THEN
  FIRST_ASSUM(MP_TAC o MATCH_MP 
DIVIDES_LE) THEN
  ASM_SIMP_TAC[ARITH_RULE `1 <= i ==> ~(i + j = 0)`] THEN
  MAP_EVERY UNDISCH_TAC [`i <= r`; `j <= r`; `2 * r + 1 = p`] THEN ARITH_TAC);;
 
let GAUSS_LEMMA_2 = prove
 (`prime p /\ coprime(a,p) /\ 2 * r + 1 = p
   ==> (nproduct(1..r) (\x. let b = (a * x) MOD p in
                            if b <= r then b else p - b) ==
        (p - 1) 
EXP (
CARD {x | x 
IN 1..r /\ r < (a * x) MOD p}) *
        a 
EXP r * nproduct(1..r) (\x. x)) (mod p)`,
 
let GAUSS_LEMMA = prove
 (`!a p r. prime p /\ coprime(a,p) /\ 2 * r + 1 = p
           ==> (a 
is_quadratic_residue (mod p) <=>
                
EVEN(
CARD {x | x 
IN 1..r /\ r < (a * x) MOD p}))`,
  REPEAT GEN_TAC THEN DISCH_TAC THEN
  MATCH_MP_TAC 
CONG_COND_LEMMA THEN EXISTS_TAC `p:num` THEN CONJ_TAC THENL
   [FIRST_X_ASSUM(CONJUNCTS_THEN2 MP_TAC STRIP_ASSUME_TAC) THEN
    EXPAND_TAC "p" THEN ASM_CASES_TAC `r = 0` THENL
     [REWRITE_TAC[ASSUME `r = 0`; ARITH; 
PRIME_1];
      UNDISCH_TAC `~(r = 0)` THEN ARITH_TAC];
    FIRST_ASSUM(MP_TAC o MATCH_MP 
GAUSS_LEMMA_4) THEN
    REPEAT(COND_CASES_TAC THEN ASM_SIMP_TAC[
CONG_REFL]) THEN
    REWRITE_TAC[
MULT_CLAUSES] THEN MESON_TAC[
CONG_SYM]]);;
 
let RECIPROCITY_SIMPLE = prove
 (`!p q r s.
        prime p /\
        prime q /\
        coprime (p,q) /\
        2 * r + 1 = p /\
        2 * s + 1 = q
        ==> ((q 
is_quadratic_residue (mod p) <=>
              p 
is_quadratic_residue (mod q)) <=>
             ~(
ODD r /\ 
ODD s))`,
  REPEAT STRIP_TAC THEN
  MP_TAC(SPECL [`p:num`; `q:num`; `r:num`; `s:num`] 
GAUSS_LEMMA_SYM) THEN
  MP_TAC(SPECL [`p:num`; `q:num`; `r:num`; `s:num`] GAUSS_LEMMA_SYM') THEN
  ASM_REWRITE_TAC[] THEN
  ONCE_REWRITE_TAC[
COPRIME_SYM] THEN ASM_REWRITE_TAC[] THEN
  REPEAT(DISCH_THEN SUBST1_TAC) THEN MATCH_MP_TAC 
RECIPROCITY_SET_LEMMA THEN
  EXISTS_TAC `{x,y | x 
IN 1..r /\ y 
IN 1..s /\ q * x + r < p * y}` THEN
  EXISTS_TAC `{x,y | x 
IN 1..r /\ y 
IN 1..s /\ p * y + s < q * x}` THEN
  REPEAT CONJ_TAC THEN
  REWRITE_TAC[
PAIRWISE; 
DISJOINT; 
EXTENSION; 
NOT_IN_EMPTY; 
FORALL_PAIR_THM;
              
ALL; 
IN_UNION; 
IN_CROSS; 
IN_ELIM_PAIR_THM; 
IN_INTER]
  THENL
   [MAP_EVERY X_GEN_TAC [`x:num`; `y:num`] THEN
    MAP_EVERY ASM_CASES_TAC [`x 
IN 1..r`; `y 
IN 1..s`] THEN ASM_SIMP_TAC[] THEN
    SUBGOAL_THEN `~(q * x = p * y)` (fun th -> MP_TAC th THEN ARITH_TAC) THEN
    DISCH_THEN(MP_TAC o AP_TERM `(divides) p`) THEN
    ASM_SIMP_TAC[
PRIME_DIVPROD_EQ; 
DIVIDES_REFL] THEN STRIP_TAC THENL
     [ASM_MESON_TAC[
DIVIDES_REFL; 
PRIME_1; coprime]; ALL_TAC] THEN
    FIRST_X_ASSUM(MP_TAC o MATCH_MP 
DIVIDES_LE) THEN
    UNDISCH_TAC `x 
IN 1..r` THEN REWRITE_TAC[
IN_NUMSEG] THEN
    EXPAND_TAC "p" THEN ARITH_TAC;
    ARITH_TAC;
    MATCH_MP_TAC 
BIJECTIONS_CARD_EQ THEN
    REPEAT(EXISTS_TAC `\(x,y). (r + 1) - x,(s + 1) - y`) THEN
    SIMP_TAC[
FINITE_SUBCROSS; 
FINITE_NUMSEG] THEN
    REWRITE_TAC[
FORALL_PAIR_THM; 
IN_ELIM_PAIR_THM; 
IN_NUMSEG; 
PAIR_EQ] THEN
    CONJ_TAC THEN MAP_EVERY X_GEN_TAC [`x:num`; `y:num`] THEN
    SIMP_TAC[ARITH_RULE `x <= y ==> (y + 1) - ((y + 1) - x) = x`] THEN
    SIMP_TAC[ARITH_RULE
     `1 <= x /\ x <= y ==> 1 <= (y + 1) - x /\ (y + 1) - x <= y`] THEN
    REWRITE_TAC[
LEFT_SUB_DISTRIB] THEN REPEAT STRIP_TAC THEN
    MATCH_MP_TAC(ARITH_RULE
     `x <= y /\ v + y + z < x + u ==> (y - x) + z < u - v`) THEN
    ASM_SIMP_TAC[
LE_MULT_LCANCEL; ARITH_RULE `x <= r ==> x <= r + 1`] THEN
    REWRITE_TAC[ARITH_RULE `a + x < y + a <=> x < y`] THEN
    REPEAT(FIRST_X_ASSUM(SUBST_ALL_TAC o SYM)) THEN
    ASM_ARITH_TAC]);;
 
let RECIPROCITY_LEGENDRE = prove
 (`!p q. prime p /\ prime q /\ 
ODD p /\ 
ODD q /\ ~(p = q)
         ==> legendre(p,q) * legendre(q,p) =
             --(&1) pow ((p - 1) DIV 2 * (q - 1) DIV 2)`,