(* ========================================================================= *)
(* Part 1: Background theories.                                              *)
(* ========================================================================= *)
(* ------------------------------------------------------------------------- *)
(* Definition of type of finite multisets with a few basic operations.       *)
(* ------------------------------------------------------------------------- *)
parse_as_infix("mmember",(11,"right"));;
parse_as_infix("munion",(16,"right"));;
parse_as_infix("mdiff",(18,"left"));;
let multiset_tybij = new_type_definition
  "multiset" ("multiset","multiplicity") multiset_tybij_th;;
(* ------------------------------------------------------------------------- *)
(* Extensionality for multisets.                                             *)
(* ------------------------------------------------------------------------- *)
let MEXTENSION = prove
 (`(M = N) = !a. multiplicity M a = multiplicity N a`,
  REWRITE_TAC[GSYM 
FUN_EQ_THM] THEN CONV_TAC(ONCE_DEPTH_CONV ETA_CONV) THEN
  MESON_TAC[multiset_tybij]);;
 
 
(* ------------------------------------------------------------------------- *)
(* Basic properties of multisets.                                            *)
(* ------------------------------------------------------------------------- *)
let MSING = prove
 (`multiplicity (msing (a:A)) b = if b = a then 1 else 0`,
 
 
let MUNION = prove
 (`multiplicity (M munion N) a = multiplicity M a + multiplicity N a`,
 
 
let MDIFF = prove
 (`multiplicity (M mdiff N) (a:A) = multiplicity M a - multiplicity N a`,
  REWRITE_TAC[mdiff] THEN MATCH_MP_TAC 
MULTIPLICITY_MULTISET THEN
  REWRITE_TAC[] THEN MATCH_MP_TAC 
FINITE_SUBSET THEN
  EXISTS_TAC `{a:A | ~(multiplicity M a = 0)}` THEN
  SIMP_TAC[
SUBSET; 
IN_ELIM_THM; multiset_tybij] THEN
  CONV_TAC(ONCE_DEPTH_CONV ETA_CONV) THEN REWRITE_TAC[multiset_tybij] THEN
  ARITH_TAC);;
 
 
(* ------------------------------------------------------------------------- *)
(* Some trivial properties of multisets that we use later.                   *)
(* ------------------------------------------------------------------------- *)
let MUNION_AC = prove
 (`(M1 munion M2 = M2 munion M1) /\
   ((M1 munion M2) munion M3 = M1 munion M2 munion M3) /\
   (M1 munion M2 munion M3 = M2 munion M1 munion M3)`,
 
 
let MMEMBER_MDIFF = prove
 (`(a:A) mmember M ==> (M = (M mdiff (msing a)) munion (msing a))`,
  REWRITE_TAC[mmember; 
MEXTENSION; 
MUNION; 
MDIFF; 
MSING] THEN
  REPEAT STRIP_TAC THEN COND_CASES_TAC THEN ASM_REWRITE_TAC[] THEN
  UNDISCH_TAC `~(multiplicity M (a:A) = 0)` THEN ARITH_TAC);;
 
 
(* ------------------------------------------------------------------------- *)
(* Induction principle for multisets.                                        *)
(* ------------------------------------------------------------------------- *)
let MULTISET_INDUCT_LEMMA1 = prove
 (`(!M. ({a | ~(multiplicity M a = 0)} 
SUBSET s) ==> P M) /\
   (!a:A M. P M ==> P (M munion (msing a)))
   ==> !n M. (multiplicity M a = n) /\
             {a:A | ~(multiplicity M a = 0)} 
SUBSET (a 
INSERT s)
             ==> P M`,
  STRIP_TAC THEN INDUCT_TAC THEN REPEAT STRIP_TAC THENL
   [FIRST_X_ASSUM MATCH_MP_TAC THEN
    UNDISCH_TAC `{a:A | ~(multiplicity M a = 0)} 
SUBSET (a 
INSERT s)` THEN
    REWRITE_TAC[
SUBSET; 
IN_ELIM_THM; 
IN_INSERT] THEN ASM_MESON_TAC[];
    SUBGOAL_THEN `M = (M mdiff (msing(a:A))) munion (msing a)` SUBST1_TAC THENL
     [MATCH_MP_TAC 
MMEMBER_MDIFF THEN ASM_REWRITE_TAC[mmember; 
NOT_SUC];
      ALL_TAC] THEN
    MAP_EVERY (MATCH_MP_TAC o ASSUME)
     [`!a:A M. P M ==> P (M munion msing a)`;
      `!M. (multiplicity M a = n) /\
           {a:A | ~(multiplicity M a = 0)} 
SUBSET (a 
INSERT s)
           ==> P M`] THEN
    ASM_REWRITE_TAC[
MDIFF; 
MSING; ARITH_RULE `SUC n - 1 = n`] THEN
    MATCH_MP_TAC 
SUBSET_TRANS THEN
    EXISTS_TAC `{a:A | ~(multiplicity M a = 0)}` THEN
    ASM_SIMP_TAC[
SUBSET; 
IN_ELIM_THM; CONTRAPOS_THM; 
SUB_0]]);;
 
 
let MULTISET_INDUCT = prove
 (`P mempty /\
   (!a:A M. P M ==> P (M munion (msing a)))
   ==> !M. P M`,
  DISCH_THEN(MP_TAC o MATCH_MP 
MULTISET_INDUCT_LEMMA2) THEN
  REWRITE_TAC[
RIGHT_IMP_FORALL_THM] THEN
  REWRITE_TAC[IMP_IMP] THEN
  GEN_TAC THEN DISCH_THEN MATCH_MP_TAC THEN
  EXISTS_TAC `{a:A | ~(multiplicity M a = 0)}` THEN
  REWRITE_TAC[
SUBSET_REFL; multiset_tybij] THEN
  CONV_TAC(ONCE_DEPTH_CONV ETA_CONV) THEN REWRITE_TAC[multiset_tybij]);;
 
 
(* ========================================================================= *)
(* Part 2: Transcription of Tobias's paper.                                  *)
(* ========================================================================= *)
parse_as_infix("<<",(12,"right"));;
(* ------------------------------------------------------------------------- *)
(* Wellfounded part of a relation.                                           *)
(* ------------------------------------------------------------------------- *)
let WFP_RULES,WFP_INDUCT,WFP_CASES = new_inductive_definition
  `!x. (!y. y << x ==> WFP(<<) y) ==> WFP(<<) x`;;
(* ------------------------------------------------------------------------- *)
(* Wellfounded part induction.                                               *)
(* ------------------------------------------------------------------------- *)
let WFP_PART_INDUCT = prove
 (`!P. (!x. x 
IN WFP(<<) /\ (!y. y << x ==> P(y)) ==> P(x))
       ==> !x:A. x 
IN WFP(<<) ==> P(x)`,
  GEN_TAC THEN REWRITE_TAC[
IN] THEN STRIP_TAC THEN
  ONCE_REWRITE_TAC[TAUT `a ==> b <=> a ==> a /\ b`] THEN
  MATCH_MP_TAC 
WFP_INDUCT THEN ASM_MESON_TAC[
WFP_RULES]);;
 
 
(* ------------------------------------------------------------------------- *)
(* A relation is wellfounded iff WFP is the whole universe.                  *)
(* ------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------- *)
(* The multiset order.                                                       *)
(* ------------------------------------------------------------------------- *)
let morder = new_definition
  `morder(<<) N M <=> ?M0 a K. (M = M0 munion (msing a)) /\
                               (N = M0 munion K) /\
                               (!b. b mmember K ==> b << a)`;; 
(* ------------------------------------------------------------------------- *)
(* We separate off this part from the proof of LEMMA_2_1.                    *)
(* ------------------------------------------------------------------------- *)
let LEMMA_2_0 = prove
 (`morder(<<) N (M0 munion (msing a))
   ==> (?M. morder(<<) M M0 /\ (N = M munion (msing a))) \/
       (?K. (N = M0 munion K) /\ (!b:A. b mmember K ==> b << a))`,
  GEN_REWRITE_TAC LAND_CONV [morder] THEN
  DISCH_THEN(EVERY_TCL (map X_CHOOSE_THEN
   [`M1:(A)multiset`; `b:A`; `K:(A)multiset`]) STRIP_ASSUME_TAC) THEN
  ASM_CASES_TAC `b:A = a` THENL
   [DISJ2_TAC THEN UNDISCH_THEN `b:A = a` SUBST_ALL_TAC THEN
    EXISTS_TAC `K:(A)multiset` THEN ASM_MESON_TAC[
MUNION_11]; DISJ1_TAC] THEN
  SUBGOAL_THEN `?M2. M1 = M2 munion (msing(a:A))` STRIP_ASSUME_TAC THENL
   [EXISTS_TAC `M1 mdiff (msing(a:A))` THEN
    MAP_EVERY MATCH_MP_TAC [
MMEMBER_MDIFF; 
MUNION_INUNION] THEN
    UNDISCH_TAC `M0 munion (msing a) = M1 munion (msing(b:A))` THEN
    ASM_REWRITE_TAC[
MEXTENSION; 
MUNION; 
MSING; mmember] THEN
    DISCH_THEN(MP_TAC o SPEC `a:A`) THEN ASM_REWRITE_TAC[] THEN
    ARITH_TAC; ALL_TAC] THEN
  EXISTS_TAC `M2 munion K:(A)multiset` THEN ASM_REWRITE_TAC[
MUNION_AC] THEN
  REWRITE_TAC[morder] THEN
  MAP_EVERY EXISTS_TAC [`M2:(A)multiset`; `b:A`; `K:(A)multiset`] THEN
  UNDISCH_TAC `M0 munion msing (a:A) = M1 munion msing b` THEN
  ASM_REWRITE_TAC[
MUNION_AC] THEN MESON_TAC[
MUNION_AC; 
MUNION_11]);;
 
 
(* ------------------------------------------------------------------------- *)
(* The sequence of lemmas from Tobias's paper.                               *)
(* ------------------------------------------------------------------------- *)
let LEMMA_2_1 = prove
 (`(!M b:A. b << a /\ M 
IN WFP(morder(<<))
            ==> (M munion (msing b)) 
IN WFP(morder(<<))) /\
   M0 
IN WFP(morder(<<)) /\
   (!M. morder(<<) M M0 ==> (M munion (msing a)) 
IN WFP(morder(<<)))
   ==> (M0 munion (msing a)) 
IN WFP(morder(<<))`,
 
 
(* ------------------------------------------------------------------------- *)
(* Hence the final result.                                                   *)
(* ------------------------------------------------------------------------- *)