File ‹wlog.ML›
structure Wlog : sig
val wlog : Position.T ->
binding * attribute list * term list ->
term ->
(binding * string * typ) list ->
(string * thm list) list ->
bool ->
Proof.state -> Proof.state
val wlog_cmd : Position.T -> Attrib.binding * string list -> string -> binding list ->
(Facts.ref * Token.src list) list -> bool -> Proof.state -> Proof.state
end = struct
fun print_entity_binding entity binding =
Pretty.marks_str ([Position.entity_markup entity (Binding.name_of binding, Binding.pos_of binding)], Binding.name_of binding) |> Pretty.string_of
val print_fact_binding = print_entity_binding Markup.factN
fun print_term_as_statement ctxt heading term : string =
Element.pretty_statement ctxt heading (Thm.assume (Thm.cterm_of ctxt term)) |> Pretty.string_of
val wlog_recovered_facts_prefix = "wlog_keep"
fun binding_within qualifier pos name = let
val (path, name) = split_last (Long_Name.explode name)
val path = case path of "local" :: path => path | path => path
val path = case path of p :: ps => if p = wlog_recovered_facts_prefix then ps else ps | ps => ps
val binding = Binding.make (name,pos)
|> fold_rev (fn qualifier => fn b => Binding.qualify true qualifier b) path
|> Binding.qualify true qualifier
in binding end
fun prove_prem_if_possible ctxt facts i thm = let
val prem = nth (Thm.prems_of thm) (i-1)
val candidates = Facts.could_unify facts prem |> map fst
val result = solve_tac ctxt candidates i thm |> Seq.pull |> Option.map fst
in case result of SOME thm => thm | NONE => thm end
fun translate_thm ctxt fixes fixed thm =
let val hyps = Thm.chyps_of thm
val thm = fold_rev Thm.implies_intr hyps thm
val idx = Thm.maxidx_of thm + 1
val thm = Thm.generalize (Names.empty, map #2 fixes |> Names.make_set) idx thm
val thm = thm |> Thm.instantiate (TVars.empty,
map2 (fn (_,n,T) => fn m => (((n,idx),T), Thm.cterm_of ctxt (Free (m,T)))) fixes fixed |> Vars.make)
val facts = Proof_Context.facts_of ctxt
val thm = fold (prove_prem_if_possible ctxt facts) (length hyps downto 1) thm
in thm end
fun strip_Trueprop _ (Const(@{const_name Trueprop},_) $ t) = t
| strip_Trueprop ctxt t = error ("The wlog-assumption must be of type bool (i.e., don't use ⟹, ⋀, &&&). You specified: " ^ Syntax.string_of_term ctxt t)
fun mk_conj_list [] = \<^term>‹True›
| mk_conj_list [t] = t
| mk_conj_list (t::ts) = HOLogic.mk_conj (t, mk_conj_list ts)
fun negate_conj ctxt props = let
val props_bool = map (strip_Trueprop ctxt) props
val conj = mk_conj_list props_bool
in
HOLogic.mk_Trueprop (HOLogic.mk_not conj)
end
fun assume_conj_tac ctxt n 0 i = error "assume_conj_tac: j=0"
| assume_conj_tac ctxt 1 1 i = assume_tac ctxt i
| assume_conj_tac ctxt n 1 i =
if n < 2 then error "assume_conj_tac: n<1"
else (dresolve_tac ctxt [@{lemma ‹a∧b ⟹ a› by simp}] i THEN assume_tac ctxt i)
| assume_conj_tac ctxt n j i =
if j < 0 then error "assume_conj_tac: j<0"
else if j > n then error "assume_conj_tac: j>n"
else (dresolve_tac ctxt [@{lemma ‹a∧b ⟹ b› by simp}] i THEN assume_conj_tac ctxt (n-1) (j-1) i)
fun counter_tac _ 0 _ = all_tac
| counter_tac tac n i = tac 1 i THEN (counter_tac (fn j => tac (j+1)) (n-1) i)
fun wlog_aux_tac ctxt thm hyp_thm n assms = let
in
resolve_tac ctxt (@{thms HOL.case_split}) 1
THEN solve_tac ctxt [thm] 2
THEN resolve_tac ctxt [hyp_thm] 1
THEN counter_tac (assume_conj_tac ctxt n) n 1
THEN ALLGOALS (fn i => (resolve_tac ctxt [nth assms (i-1)] i))
end
fun wlog (pos:Position.T)
(newassm_name, newassm_attribs, newassm)
(goal: term)
(fixes: (binding*string*typ) list)
(assms: (string*thm list) list)
(int: bool)
(state: Proof.state) : Proof.state =
let
val initial_ctxt = Proof.context_of state
val flat_assms = ((Binding.name_of newassm_name, newassm) ::
map (fn (n,thm) => (n, map Thm.prop_of thm)) assms)
|> map (fn (name,thms) => case thms of
[t] => [(name,0,t)]
| _ => map_index (fn (i,t) => (name,i+1,t)) thms)
|> List.concat
val hyp = Logic.list_implies (map #3 flat_assms, goal)
val hyp = fold (fn (_,a,T) => fn t => Logic.all_const T $ (Term.absfree (a,T) t)) fixes hyp
fun idx_name (name, 0) = name
| idx_name (name, i) = name ^ "_" ^ string_of_int i
val case_names = map (fn (name,i,_) => idx_name(name,i)) flat_assms
val case_names = Rule_Cases.cases_hyp_names case_names (map (K []) case_names)
val negated_newassm = negate_conj initial_ctxt newassm
val newassm_name_text =
if Binding.name_of newassm_name = ""
then String.concatWith " " (map (fn t => "‹" ^ Syntax.string_of_term initial_ctxt t ^ "›") newassm)
else "\"" ^ print_fact_binding newassm_name ^ "\""
val _ = Output.information ("Please prove that " ^ newassm_name_text ^ " can be assumed w.l.o.g.\nYou may use the following facts:\n" ^
print_term_as_statement initial_ctxt "hypothesis:" hyp ^ "\n" ^ print_term_as_statement initial_ctxt "negation:" negated_newassm)
val state = Proof.presume [] [] [((Binding.make ("hypothesis", pos), [case_names]), [(hyp,[])])] state
val hyp_thm = Proof.the_fact state
fun after_qed _ state =
let
val _ = Output.information "Setting up everything after wlog command.\nAny errors below this are from the wlog command, not from the proof you just finished."
val proven_thm = Proof.the_fact state
val (_,state) = Proof.show true NONE (fn _ => I) [] []
[((Binding.empty,[]),[(goal,[])])] int state
val state = Proof.apply (Method.Basic (fn ctxt =>
(Method.SIMPLE_METHOD (wlog_aux_tac ctxt proven_thm hyp_thm (length newassm) (assms |> map snd |> List.concat)))),
Position.no_range) state
|> Seq.the_result "internal error: negation_tac failed"
val state = Proof.local_done_proof state
val state = Proof.next_block state
val (fixed,state) = Proof.map_context_result (Proof_Context.add_fixes (map (fn (a,_,T) => (a,SOME T,NoSyn)) fixes)) state
val rename_fixed = Term.subst_free (map2 (fn (_,a,T) => fn b => (Free (a,T), Free(b,T))) fixes fixed)
val let_bindings = Variable.binds_of initial_ctxt |> Vartab.dest
val state = fold (fn (name,(_,t)) => Proof.map_context (Variable.bind_term (name, rename_fixed t))) let_bindings state
val state = Proof.map_context (Variable.bind_term (("wlog_goal",0), rename_fixed goal)) state
val state = Proof.assume [] [] (map (fn (name, assm) => ((Binding.make (name, pos),[]), map (fn t => (rename_fixed (Thm.prop_of t),[])) assm)) assms) state
val state = Proof.note_thmss [((Binding.qualified_name "wlog_assms" |> Binding.set_pos pos, []), [(Proof.the_facts state, [])])] state
val facts = Proof_Context.facts_of initial_ctxt
val lost_facts = Facts.dest_static false [Proof_Context.facts_of (Proof.context_of state)] facts
|> filter (fn (name,_) => name <> "local.this")
val state = Proof.note_thmss (map (fn (name,thms) => ((binding_within wlog_recovered_facts_prefix pos name, []),
[(map (translate_thm (Proof.context_of state) fixes fixed) thms, [])])) lost_facts) state
val state = Proof.assume [] [] [((newassm_name, newassm_attribs), map (fn t => (rename_fixed t,[])) newassm)] state
val _ = Output.information "Use the print_theorems command to see the automatically generated/recovered facts."
in state end
val (_,state) = Proof.have true NONE after_qed []
[((Binding.make ("negation", pos),[]), [(negated_newassm, [])])]
[((Binding.empty,[]), [(goal,[])])] int state
in state end
fun wlog_cmd (pos: Position.T)
(((bind,attrib),stmt) : Attrib.binding * string list)
(goal: string)
(fixes : binding list)
(assms : (Facts.ref * Token.src list) list)
int state =
let val ctxt = Proof.context_of state
val stmt = map (Syntax.read_prop ctxt) stmt
val assms' = map (fn (fact,_) => (Facts.ref_name fact, Proof_Context.get_fact ctxt fact)) assms
val goal' = Syntax.read_prop ctxt goal
val constr = Variable.constraints_of ctxt |> #1
val fixes' = map (fn b => let val internal = Variable.lookup_fixed ctxt (Binding.name_of b) |> Option.valOf
val T = Vartab.lookup constr (internal,~1) |> Option.valOf
in (b,internal,T) end) fixes
val attrib2 = map (Attrib.attribute_cmd ctxt) attrib
in wlog pos (bind,attrib2,stmt) goal' fixes' assms' int state end
val wlog_parser = (Parse_Spec.opt_thm_name ":" -- Scan.repeat1 Parse.prop) --
(Scan.optional (@{keyword "goal"} |-- Parse.prop) "?thesis") --
(Scan.optional (@{keyword "generalizing"} |-- Scan.repeat Parse.binding) []) --
(Scan.optional (@{keyword "keeping"} |-- Parse.thms1) [])
|> Parse.position
val _ =
Outer_Syntax.command @{command_keyword wlog} "Adds an assumption that holds without loss of generality"
(wlog_parser >> (fn ((((stmt,goal),fixes),assms),pos) => Toplevel.proof' (wlog_cmd pos stmt goal fixes assms)));
end