src/HOL/Tools/Sledgehammer/sledgehammer_isar_compress.ML
changeset 55202 824c48a539c9
parent 55183 17ec4a29ef71
child 55212 5832470d956e
equal deleted inserted replaced
55201:1ee776da8da7 55202:824c48a539c9
       
     1 (*  Title:      HOL/Tools/Sledgehammer/sledgehammer_isar_compress.ML
       
     2     Author:     Steffen Juilf Smolka, TU Muenchen
       
     3     Author:     Jasmin Blanchette, TU Muenchen
       
     4 
       
     5 Compression of Isar proofs by merging steps.
       
     6 Only proof steps using the same proof method are merged.
       
     7 *)
       
     8 
       
     9 signature SLEDGEHAMMER_ISAR_COMPRESS =
       
    10 sig
       
    11   type isar_proof = Sledgehammer_Isar_Proof.isar_proof
       
    12   type preplay_interface = Sledgehammer_Isar_Preplay.preplay_interface
       
    13 
       
    14   val compress_proof : real -> preplay_interface -> isar_proof -> isar_proof
       
    15 end;
       
    16 
       
    17 structure Sledgehammer_Isar_Compress : SLEDGEHAMMER_ISAR_COMPRESS =
       
    18 struct
       
    19 
       
    20 open Sledgehammer_Util
       
    21 open Sledgehammer_Reconstructor
       
    22 open Sledgehammer_Isar_Proof
       
    23 open Sledgehammer_Isar_Preplay
       
    24 
       
    25 (* traverses steps in post-order and collects the steps with the given labels *)
       
    26 fun collect_successors steps lbls =
       
    27   let
       
    28     fun do_steps _ ([], accu) = ([], accu)
       
    29       | do_steps [] accum = accum
       
    30       | do_steps (step :: steps) accum = do_steps steps (do_step step accum)
       
    31     and do_step (Let _) x = x
       
    32       | do_step (step as Prove (_, _, l, _, subproofs, _)) x =
       
    33         (case do_subproofs subproofs x of
       
    34           ([], accu) => ([], accu)
       
    35         | accum as (l' :: lbls', accu) => if l = l' then (lbls', step :: accu) else accum)
       
    36     and do_subproofs [] x = x
       
    37       | do_subproofs (proof :: subproofs) x =
       
    38         (case do_steps (steps_of_proof proof) x of
       
    39           accum as ([], _) => accum
       
    40         | accum => do_subproofs subproofs accum)
       
    41   in
       
    42     (case do_steps steps (lbls, []) of
       
    43       ([], succs) => rev succs
       
    44     | _ => raise Fail "Sledgehammer_Isar_Compress: collect_successors")
       
    45   end
       
    46 
       
    47 (* traverses steps in reverse post-order and inserts the given updates *)
       
    48 fun update_steps steps updates =
       
    49   let
       
    50     fun do_steps [] updates = ([], updates)
       
    51       | do_steps steps [] = (steps, [])
       
    52       | do_steps (step :: steps) updates = do_step step (do_steps steps updates)
       
    53     and do_step step (steps, []) = (step :: steps, [])
       
    54       | do_step (step as Let _) (steps, updates) = (step :: steps, updates)
       
    55       | do_step (Prove (qs, xs, l, t, subproofs, by))
       
    56           (steps, updates as Prove (qs', xs', l', t', subproofs', by') :: updates') =
       
    57         let
       
    58           val (subproofs, updates) =
       
    59             if l = l' then do_subproofs subproofs' updates' else do_subproofs subproofs updates
       
    60         in
       
    61           if l = l' then (Prove (qs', xs', l', t', subproofs, by') :: steps, updates)
       
    62           else (Prove (qs, xs, l, t, subproofs, by) :: steps, updates)
       
    63         end
       
    64       | do_step _ _ = raise Fail "Sledgehammer_Isar_Compress: update_steps (invalid update)"
       
    65     and do_subproofs [] updates = ([], updates)
       
    66       | do_subproofs steps [] = (steps, [])
       
    67       | do_subproofs (proof :: subproofs) updates =
       
    68         do_proof proof (do_subproofs subproofs updates)
       
    69     and do_proof proof (proofs, []) = (proof :: proofs, [])
       
    70       | do_proof (Proof (fix, assms, steps)) (proofs, updates) =
       
    71         let val (steps, updates) = do_steps steps updates in
       
    72           (Proof (fix, assms, steps) :: proofs, updates)
       
    73         end
       
    74   in
       
    75     (case do_steps steps (rev updates) of
       
    76       (steps, []) => steps
       
    77     | _ => raise Fail "Sledgehammer_Isar_Compress: update_steps")
       
    78   end
       
    79 
       
    80 (* Tries merging the first step into the second step. Arbitrarily picks the second step's method. *)
       
    81 fun try_merge (Prove (_, [], lbl1, _, [], ((lfs1, gfs1), _)))
       
    82       (Prove (qs2, fix, lbl2, t, subproofs, ((lfs2, gfs2), meth2))) =
       
    83     let
       
    84       val lfs = remove (op =) lbl1 lfs2 |> union (op =) lfs1
       
    85       val gfs = union (op =) gfs1 gfs2
       
    86     in
       
    87       SOME (Prove (qs2, fix, lbl2, t, subproofs, ((lfs, gfs), meth2)))
       
    88     end
       
    89   | try_merge _ _ = NONE
       
    90 
       
    91 val compress_degree = 2
       
    92 val merge_timeout_slack = 1.2
       
    93 
       
    94 (* Precondition: The proof must be labeled canonically
       
    95    (cf. "Slegehammer_Proof.relabel_proof_canonically"). *)
       
    96 fun compress_proof compress_isar
       
    97     ({get_preplay_outcome, set_preplay_outcome, preplay_quietly, ...} : preplay_interface) proof =
       
    98   if compress_isar <= 1.0 then
       
    99     proof
       
   100   else
       
   101     let
       
   102       val (compress_further, decrement_step_count) =
       
   103         let
       
   104           val number_of_steps = add_proof_steps (steps_of_proof proof) 0
       
   105           val target_number_of_steps = Real.round (Real.fromInt number_of_steps / compress_isar)
       
   106           val delta = Unsynchronized.ref (number_of_steps - target_number_of_steps)
       
   107         in
       
   108           (fn () => !delta > 0, fn () => delta := !delta - 1)
       
   109         end
       
   110 
       
   111       val (get_successors, replace_successor) =
       
   112         let
       
   113           fun add_refs (Let _) = I
       
   114             | add_refs (Prove (_, _, v, _, _, ((lfs, _), _))) =
       
   115               fold (fn key => Canonical_Lbl_Tab.cons_list (key, v)) lfs
       
   116 
       
   117           val tab =
       
   118             Canonical_Lbl_Tab.empty
       
   119             |> fold_isar_steps add_refs (steps_of_proof proof)
       
   120             (* "rev" should have the same effect as "sort canonical_label_ord" *)
       
   121             |> Canonical_Lbl_Tab.map (K rev)
       
   122             |> Unsynchronized.ref
       
   123 
       
   124           fun get_successors l = Canonical_Lbl_Tab.lookup_list (!tab) l
       
   125           fun set_successors l refs = tab := Canonical_Lbl_Tab.update (l, refs) (!tab)
       
   126           fun replace_successor old new dest =
       
   127             get_successors dest
       
   128             |> Ord_List.remove canonical_label_ord old
       
   129             |> Ord_List.union canonical_label_ord new
       
   130             |> set_successors dest
       
   131         in
       
   132           (get_successors, replace_successor)
       
   133         end
       
   134 
       
   135       (** elimination of trivial, one-step subproofs **)
       
   136 
       
   137       fun elim_subproofs' time qs fix l t lfs gfs meth subs nontriv_subs =
       
   138         if null subs orelse not (compress_further ()) then
       
   139           (set_preplay_outcome l (Played time);
       
   140            Prove (qs, fix, l, t, List.revAppend (nontriv_subs, subs), ((lfs, gfs), meth)))
       
   141         else
       
   142           (case subs of
       
   143             (sub as Proof (_, assms, sub_steps)) :: subs =>
       
   144             (let
       
   145               (* trivial subproofs have exactly one Prove step *)
       
   146               val SOME (Prove (_, [], l', _, [], ((lfs', gfs'), _))) = try the_single sub_steps
       
   147 
       
   148               (* only touch proofs that can be preplayed sucessfully *)
       
   149               val Played time' = get_preplay_outcome l'
       
   150 
       
   151               (* merge steps *)
       
   152               val subs'' = subs @ nontriv_subs
       
   153               val lfs'' =
       
   154                 subtract (op =) (map fst assms) lfs'
       
   155                 |> union (op =) lfs
       
   156               val gfs'' = union (op =) gfs' gfs
       
   157               val by = ((lfs'', gfs''), meth)
       
   158               val step'' = Prove (qs, fix, l, t, subs'', by)
       
   159 
       
   160               (* check if the modified step can be preplayed fast enough *)
       
   161               val timeout = time_mult merge_timeout_slack (Time.+(time, time'))
       
   162               val Played time'' = preplay_quietly timeout step''
       
   163 
       
   164             in
       
   165               decrement_step_count (); (* l' successfully eliminated! *)
       
   166               map (replace_successor l' [l]) lfs';
       
   167               elim_subproofs' time'' qs fix l t lfs'' gfs'' meth subs nontriv_subs
       
   168             end
       
   169             handle Bind =>
       
   170               elim_subproofs' time qs fix l t lfs gfs meth subs (sub :: nontriv_subs))
       
   171           | _ => raise Fail "Sledgehammer_Isar_Compress: elim_subproofs'")
       
   172 
       
   173       fun elim_subproofs (step as Let _) = step
       
   174         | elim_subproofs (step as Prove (qs, fix, l, t, subproofs, ((lfs, gfs), meth))) =
       
   175           if subproofs = [] then
       
   176             step
       
   177           else
       
   178             (case get_preplay_outcome l of
       
   179               Played time => elim_subproofs' time qs fix l t lfs gfs meth subproofs []
       
   180             | _ => step)
       
   181 
       
   182       (** top_level compression: eliminate steps by merging them into their
       
   183           successors **)
       
   184 
       
   185       fun compress_top_level steps =
       
   186         let
       
   187           (* (#successors, (size_of_term t, position)) *)
       
   188           fun cand_key (i, l, t_size) = (get_successors l |> length, (t_size, i))
       
   189 
       
   190           val compression_ord =
       
   191             prod_ord int_ord (prod_ord (int_ord #> rev_order) int_ord)
       
   192             #> rev_order
       
   193 
       
   194           val cand_ord = pairself cand_key #> compression_ord
       
   195 
       
   196           fun pop_next_cand candidates =
       
   197             (case max_list cand_ord candidates of
       
   198               NONE => (NONE, [])
       
   199             | cand as SOME (i, _, _) => (cand, filter_out (fn (j, _, _) => j = i) candidates))
       
   200 
       
   201           val candidates =
       
   202             let
       
   203               fun add_cand (_, Let _) = I
       
   204                 | add_cand (i, Prove (_, _, l, t, _, _)) = cons (i, l, size_of_term t)
       
   205             in
       
   206               (steps
       
   207               |> split_last |> fst (* keep last step *)
       
   208               |> fold_index add_cand) []
       
   209             end
       
   210 
       
   211           fun try_eliminate (i, l, _) succ_lbls steps =
       
   212             let
       
   213               (* only touch steps that can be preplayed successfully *)
       
   214               val Played time = get_preplay_outcome l
       
   215 
       
   216               val succ_times =
       
   217                 map (get_preplay_outcome #> (fn Played t => t)) succ_lbls
       
   218               val timeslice = time_mult (1.0 / (Real.fromInt (length succ_lbls))) time
       
   219               val timeouts =
       
   220                 map (curry Time.+ timeslice #> time_mult merge_timeout_slack) succ_times
       
   221 
       
   222               val ((cand as Prove (_, _, l, _, _, ((lfs, _), _))) :: steps') = drop i steps
       
   223 
       
   224               (* FIXME: debugging *)
       
   225               val _ =
       
   226                 if the (label_of_step cand) <> l then
       
   227                   raise Fail "Sledgehammer_Isar_Compress: try_eliminate"
       
   228                 else
       
   229                   ()
       
   230 
       
   231               val succs = collect_successors steps' succ_lbls
       
   232               val succs' = map (try_merge cand #> the) succs
       
   233 
       
   234               (* TODO: should be lazy: stop preplaying as soon as one step fails/times out *)
       
   235               val play_outcomes = map2 preplay_quietly timeouts succs'
       
   236 
       
   237               (* ensure none of the modified successors timed out *)
       
   238               val true = List.all (fn Played _ => true) play_outcomes
       
   239 
       
   240               val (steps1, _ :: steps2) = chop i steps
       
   241               (* replace successors with their modified versions *)
       
   242               val steps2 = update_steps steps2 succs'
       
   243             in
       
   244               decrement_step_count (); (* candidate successfully eliminated *)
       
   245               map2 set_preplay_outcome succ_lbls play_outcomes;
       
   246               map (replace_successor l succ_lbls) lfs;
       
   247               (* removing the step would mess up the indices -> replace with dummy step instead *)
       
   248               steps1 @ dummy_isar_step :: steps2
       
   249             end
       
   250             handle Bind => steps
       
   251                  | Match => steps
       
   252                  | Option.Option => steps
       
   253 
       
   254           fun compression_loop candidates steps =
       
   255             if not (compress_further ()) then
       
   256               steps
       
   257             else
       
   258               (case pop_next_cand candidates of
       
   259                 (NONE, _) => steps (* no more candidates for elimination *)
       
   260               | (SOME (cand as (_, l, _)), candidates) =>
       
   261                 let val successors = get_successors l in
       
   262                   if length successors > compress_degree then steps
       
   263                   else compression_loop candidates (try_eliminate cand successors steps)
       
   264                 end)
       
   265         in
       
   266           compression_loop candidates steps
       
   267           |> remove (op =) dummy_isar_step
       
   268         end
       
   269 
       
   270       (** recusion over the proof tree **)
       
   271       (*
       
   272          Proofs are compressed bottom-up, beginning with the innermost
       
   273          subproofs.
       
   274          On the innermost proof level, the proof steps have no subproofs.
       
   275          In the best case, these steps can be merged into just one step,
       
   276          resulting in a trivial subproof. Going one level up, trivial subproofs
       
   277          can be eliminated. In the best case, this once again leads to a proof
       
   278          whose proof steps do not have subproofs. Applying this approach
       
   279          recursively will result in a flat proof in the best cast.
       
   280       *)
       
   281       fun do_proof (proof as (Proof (fix, assms, steps))) =
       
   282         if compress_further () then Proof (fix, assms, do_steps steps) else proof
       
   283       and do_steps steps =
       
   284         (* bottom-up: compress innermost proofs first *)
       
   285         steps |> map (fn step => step |> compress_further () ? do_sub_levels)
       
   286               |> compress_further () ? compress_top_level
       
   287       and do_sub_levels (Let x) = Let x
       
   288         | do_sub_levels (Prove (qs, xs, l, t, subproofs, by)) =
       
   289           (* compress subproofs *)
       
   290           Prove (qs, xs, l, t, map do_proof subproofs, by)
       
   291           (* eliminate trivial subproofs *)
       
   292           |> compress_further () ? elim_subproofs
       
   293     in
       
   294       do_proof proof
       
   295     end
       
   296 
       
   297 end;