src/HOL/Mutabelle/mutabelle_extra.ML
author bulwahn
Mon, 17 Oct 2011 10:19:01 +0200
changeset 46029 3f1d1ce024cb
parent 45716 5e51075cbd97
child 46036 f4896c792316
permissions -rw-r--r--
moving some common functions from quickcheck to the more HOL-specific quickcheck_common; renamed inductive_SML's configurations to more canonical names; adds automatically left and right hand sides of equations as evaluation terms
     1 (*  Title:      HOL/Mutabelle/mutabelle_extra.ML
     2     Author:     Stefan Berghofer, Jasmin Blanchette, Lukas Bulwahn, TU Muenchen
     3 
     4 Invokation of Counterexample generators.
     5 *)
     6 
     7 signature MUTABELLE_EXTRA =
     8 sig
     9 
    10 val take_random : int -> 'a list -> 'a list
    11 
    12 datatype outcome = GenuineCex | PotentialCex | NoCex | Donno | Timeout | Error | Solved | Unsolved
    13 type timings = (string * int) list
    14 
    15 type mtd = string * (theory -> term -> outcome * timings)
    16 
    17 type mutant_subentry = term * (string * (outcome * timings)) list
    18 type detailed_entry = string * bool * term * mutant_subentry list
    19 
    20 type subentry = string * int * int * int * int * int * int
    21 type entry = string * bool * subentry list
    22 type report = entry list
    23 
    24 val quickcheck_mtd : (Proof.context -> Proof.context) -> string -> mtd
    25 
    26 val solve_direct_mtd : mtd
    27 val try_methods_mtd : mtd
    28 (*
    29 val sledgehammer_mtd : mtd
    30 *)
    31 val nitpick_mtd : mtd
    32 
    33 (*
    34 val refute_mtd : mtd
    35 *)
    36 
    37 val freezeT : term -> term
    38 val thms_of : bool -> theory -> thm list
    39 
    40 val string_for_report : report -> string
    41 val write_report : string -> report -> unit
    42 val mutate_theorems_and_write_report :
    43   theory -> mtd list -> thm list -> string -> unit
    44 
    45 val random_seed : real Unsynchronized.ref
    46 end;
    47 
    48 structure MutabelleExtra : MUTABELLE_EXTRA =
    49 struct
    50 
    51 (* Own seed; can't rely on the Isabelle one to stay the same *)
    52 val random_seed = Unsynchronized.ref 1.0;
    53 
    54 
    55 (* mutation options *)
    56 (*val max_mutants = 4
    57 val num_mutations = 1*)
    58 (* soundness check: *)
    59 val max_mutants =  10
    60 val num_mutations = 1
    61 
    62 (* quickcheck options *)
    63 (*val quickcheck_generator = "SML"*)
    64 
    65 (* Another Random engine *)
    66 
    67 exception RANDOM;
    68 
    69 fun rmod x y = x - y * Real.realFloor (x / y);
    70 
    71 local
    72   val a = 16807.0;
    73   val m = 2147483647.0;
    74 in
    75 
    76 fun random () = CRITICAL (fn () =>
    77   let val r = rmod (a * ! random_seed) m
    78   in (random_seed := r; r) end);
    79 
    80 end;
    81 
    82 fun random_range l h =
    83   if h < l orelse l < 0 then raise RANDOM
    84   else l + Real.floor (rmod (random ()) (real (h - l + 1)));
    85 
    86 fun take_random 0 _ = []
    87   | take_random _ [] = []
    88   | take_random n xs =
    89     let val j = random_range 0 (length xs - 1) in
    90       Library.nth xs j :: take_random (n - 1) (nth_drop j xs)
    91     end
    92   
    93 (* possible outcomes *)
    94 
    95 datatype outcome = GenuineCex | PotentialCex | NoCex | Donno | Timeout | Error | Solved | Unsolved
    96 
    97 fun string_of_outcome GenuineCex = "GenuineCex"
    98   | string_of_outcome PotentialCex = "PotentialCex"
    99   | string_of_outcome NoCex = "NoCex"
   100   | string_of_outcome Donno = "Donno"
   101   | string_of_outcome Timeout = "Timeout"
   102   | string_of_outcome Error = "Error"
   103   | string_of_outcome Solved = "Solved"
   104   | string_of_outcome Unsolved = "Unsolved"
   105 
   106 type timings = (string * int) list
   107 
   108 type mtd = string * (theory -> term -> outcome * timings)
   109 
   110 type mutant_subentry = term * (string * (outcome * timings)) list
   111 type detailed_entry = string * bool * term * mutant_subentry list
   112 
   113 type subentry = string * int * int * int * int * int * int
   114 type entry = string * bool * subentry list
   115 type report = entry list
   116 
   117 (* possible invocations *)
   118 
   119 (** quickcheck **)
   120 
   121 fun invoke_quickcheck change_options quickcheck_generator thy t =
   122   TimeLimit.timeLimit (seconds (!Try.auto_time_limit))
   123       (fn _ =>
   124           let
   125             val ctxt' = change_options (Proof_Context.init_global thy)
   126             val [result] = case Quickcheck.active_testers ctxt' of
   127               [] => error "No active testers for quickcheck"
   128             | [tester] => tester ctxt' (false, false) [] [(t, [])]
   129             | _ => error "Multiple active testers for quickcheck"
   130           in
   131             case Quickcheck.counterexample_of result of 
   132               NONE => (NoCex, Quickcheck.timings_of result)
   133             | SOME _ => (GenuineCex, Quickcheck.timings_of result)
   134           end) ()
   135   handle TimeLimit.TimeOut =>
   136          (Timeout, [("timelimit", Real.floor (!Try.auto_time_limit))])
   137 
   138 fun quickcheck_mtd change_options quickcheck_generator =
   139   ("quickcheck_" ^ quickcheck_generator, invoke_quickcheck change_options quickcheck_generator)
   140 
   141 (** solve direct **)
   142  
   143 fun invoke_solve_direct thy t =
   144   let
   145     val state = Proof.theorem NONE (K I) (map (single o rpair []) [t]) (Proof_Context.init_global thy) 
   146   in
   147     case Solve_Direct.solve_direct state of
   148       (true, _) => (Solved, [])
   149     | (false, _) => (Unsolved, [])
   150   end
   151 
   152 val solve_direct_mtd = ("solve_direct", invoke_solve_direct) 
   153 
   154 (** try **)
   155 
   156 fun invoke_try_methods thy t =
   157   let
   158     val state = Proof.theorem NONE (K I) (map (single o rpair []) [t]) (Proof_Context.init_global thy)
   159   in
   160     case Try_Methods.try_methods (SOME (seconds 5.0)) ([], [], [], []) state of
   161       true => (Solved, [])
   162     | false => (Unsolved, [])
   163   end
   164 
   165 val try_methods_mtd = ("try_methods", invoke_try_methods)
   166 
   167 (** sledgehammer **)
   168 (*
   169 fun invoke_sledgehammer thy t =
   170   if can (Goal.prove_global thy (Term.add_free_names t [])  [] t)
   171       (fn {context, ...} => Sledgehammer_Tactics.sledgehammer_with_metis_tac context 1) then
   172     (Solved, ([], NONE))
   173   else
   174     (Unsolved, ([], NONE))
   175 
   176 val sledgehammer_mtd = ("sledgehammer", invoke_sledgehammer)
   177 *)
   178 (*
   179 fun invoke_refute thy t =
   180   let
   181     val res = MyRefute.refute_term thy [] t
   182     val _ = Output.urgent_message ("Refute: " ^ res)
   183   in
   184     case res of
   185       "genuine" => GenuineCex
   186     | "likely_genuine" => GenuineCex
   187     | "potential" => PotentialCex
   188     | "none" => NoCex
   189     | "unknown" => Donno
   190     | _ => Error
   191   end
   192   handle MyRefute.REFUTE (loc, details) =>
   193          (error ("Unhandled Refute error (" ^ quote loc ^ "): " ^ details ^
   194                    "."))
   195 val refute_mtd = ("refute", invoke_refute)
   196 *)
   197 
   198 (** nitpick **)
   199 
   200 fun invoke_nitpick thy t =
   201   let
   202     val ctxt = Proof_Context.init_global thy
   203     val state = Proof.init ctxt
   204     val (res, _) = Nitpick.pick_nits_in_term state
   205       (Nitpick_Isar.default_params thy []) Nitpick.Normal 1 1 1 [] [] t
   206     val _ = Output.urgent_message ("Nitpick: " ^ res)
   207   in
   208     (rpair []) (case res of
   209       "genuine" => GenuineCex
   210     | "likely_genuine" => GenuineCex
   211     | "potential" => PotentialCex
   212     | "none" => NoCex
   213     | "unknown" => Donno
   214     | _ => Error)
   215   end
   216 
   217 val nitpick_mtd = ("nitpick", invoke_nitpick)
   218 
   219 (* filtering forbidden theorems and mutants *)
   220 
   221 val comms = [@{const_name HOL.eq}, @{const_name HOL.disj}, @{const_name HOL.conj}]
   222 
   223 val forbidden =
   224  [(* (@{const_name "power"}, "'a"), *)
   225   (*(@{const_name induct_equal}, "'a"),
   226   (@{const_name induct_implies}, "'a"),
   227   (@{const_name induct_conj}, "'a"),*)
   228   (@{const_name "undefined"}, "'a"),
   229   (@{const_name "default"}, "'a"),
   230   (@{const_name "dummy_pattern"}, "'a::{}"),
   231   (@{const_name "HOL.simp_implies"}, "prop => prop => prop"),
   232   (@{const_name "bot_fun_inst.bot_fun"}, "'a"),
   233   (@{const_name "top_fun_inst.top_fun"}, "'a"),
   234   (@{const_name "Pure.term"}, "'a"),
   235   (@{const_name "top_class.top"}, "'a"),
   236   (@{const_name "Quotient.Quot_True"}, "'a")(*,
   237   (@{const_name "uminus"}, "'a"),
   238   (@{const_name "Nat.size"}, "'a"),
   239   (@{const_name "Groups.abs"}, "'a") *)]
   240 
   241 val forbidden_thms =
   242  ["finite_intvl_succ_class",
   243   "nibble"]
   244 
   245 val forbidden_consts =
   246  [@{const_name nibble_pair_of_char}, @{const_name "TYPE"}]
   247 
   248 fun is_forbidden_theorem (s, th) =
   249   let val consts = Term.add_const_names (prop_of th) [] in
   250     exists (member (op =) (space_explode "." s)) forbidden_thms orelse
   251     exists (member (op =) forbidden_consts) consts orelse
   252     length (space_explode "." s) <> 2 orelse
   253     String.isPrefix "type_definition" (List.last (space_explode "." s)) orelse
   254     String.isSuffix "_def" s orelse
   255     String.isSuffix "_raw" s orelse
   256     String.isPrefix "term_of" (List.last (space_explode "." s))
   257   end
   258 
   259 val forbidden_mutant_constnames =
   260  ["HOL.induct_equal",
   261   "HOL.induct_implies",
   262   "HOL.induct_conj",
   263  @{const_name undefined},
   264  @{const_name default},
   265  @{const_name dummy_pattern},
   266  @{const_name "HOL.simp_implies"},
   267  @{const_name "bot_fun_inst.bot_fun"},
   268  @{const_name "top_fun_inst.top_fun"},
   269  @{const_name "Pure.term"},
   270  @{const_name "top_class.top"},
   271  (*@{const_name "HOL.equal"},*)
   272  @{const_name "Quotient.Quot_True"},
   273  @{const_name "equal_fun_inst.equal_fun"},
   274  @{const_name "equal_bool_inst.equal_bool"},
   275  @{const_name "ord_fun_inst.less_eq_fun"},
   276  @{const_name "ord_fun_inst.less_fun"},
   277  @{const_name Meson.skolem},
   278  @{const_name ATP.fequal},
   279  @{const_name transfer_morphism},
   280  @{const_name enum_prod_inst.enum_all_prod},
   281  @{const_name enum_prod_inst.enum_ex_prod}
   282  (*@{const_name "==>"}, @{const_name "=="}*)]
   283 
   284 val forbidden_mutant_consts =
   285   [
   286    (@{const_name "Groups.zero_class.zero"}, @{typ "prop => prop => prop"}),
   287    (@{const_name "Groups.one_class.one"}, @{typ "prop => prop => prop"}),
   288    (@{const_name "Groups.plus_class.plus"}, @{typ "prop => prop => prop"}),
   289    (@{const_name "Groups.minus_class.minus"}, @{typ "prop => prop => prop"}),
   290    (@{const_name "Groups.times_class.times"}, @{typ "prop => prop => prop"}),
   291    (@{const_name "Fields.inverse_class.divide"}, @{typ "prop => prop => prop"}),
   292    (@{const_name "Lattices.inf_class.inf"}, @{typ "prop => prop => prop"}),
   293    (@{const_name "Lattices.sup_class.sup"}, @{typ "prop => prop => prop"}),
   294    (@{const_name "Orderings.bot_class.bot"}, @{typ "prop => prop => prop"}),
   295    (@{const_name "Orderings.ord_class.min"}, @{typ "prop => prop => prop"}),
   296    (@{const_name "Orderings.ord_class.max"}, @{typ "prop => prop => prop"}),
   297    (@{const_name "Divides.div_class.mod"}, @{typ "prop => prop => prop"}),
   298    (@{const_name "Divides.div_class.div"}, @{typ "prop => prop => prop"}),
   299    (@{const_name "GCD.gcd_class.gcd"}, @{typ "prop => prop => prop"}),
   300    (@{const_name "GCD.gcd_class.lcm"}, @{typ "prop => prop => prop"}),
   301    (@{const_name "Orderings.bot_class.bot"}, @{typ "bool => prop"}),
   302    (@{const_name "Groups.one_class.one"}, @{typ "bool => prop"}),
   303    (@{const_name "Groups.zero_class.zero"},@{typ "bool => prop"})]
   304 
   305 fun is_forbidden_mutant t =
   306   let
   307     val const_names = Term.add_const_names t []
   308     val consts = Term.add_consts t []
   309   in
   310     exists (String.isPrefix "Nitpick") const_names orelse
   311     exists (String.isSubstring "_sumC") const_names orelse
   312     exists (member (op =) forbidden_mutant_constnames) const_names orelse
   313     exists (member (op =) forbidden_mutant_consts) consts
   314   end
   315 
   316 (* executable via quickcheck *)
   317 
   318 fun is_executable_term thy t =
   319   let
   320     val ctxt = Proof_Context.init_global thy
   321   in
   322     can (TimeLimit.timeLimit (seconds 2.0)
   323       (Quickcheck.test_terms
   324         ((Config.put Quickcheck.finite_types true #>
   325           Config.put Quickcheck.finite_type_size 1 #>
   326           Config.put Quickcheck.size 1 #> Config.put Quickcheck.iterations 1) ctxt)
   327         (false, false) [])) (map (rpair [] o Object_Logic.atomize_term thy)
   328         (fst (Variable.import_terms true [t] ctxt)))
   329   end
   330 
   331 fun is_executable_thm thy th = is_executable_term thy (prop_of th)
   332 
   333 val freezeT =
   334   map_types (map_type_tvar (fn ((a, i), S) =>
   335     TFree (if i = 0 then a else a ^ "_" ^ string_of_int i, S)))
   336 
   337 fun thms_of all thy =
   338   filter
   339     (fn th => (all orelse Context.theory_name (theory_of_thm th) = Context.theory_name thy)
   340       (* andalso is_executable_thm thy th *))
   341     (map snd (filter_out is_forbidden_theorem (Mutabelle.all_unconcealed_thms_of thy)))
   342 
   343 fun count x = (length oo filter o equal) x
   344 
   345 fun cpu_time description e =
   346   let val ({cpu, ...}, result) = Timing.timing e ()
   347   in (result, (description, Time.toMilliseconds cpu)) end
   348 (*
   349 fun unsafe_invoke_mtd thy (mtd_name, invoke_mtd) t =
   350   let
   351     val _ = Output.urgent_message ("Invoking " ^ mtd_name)
   352     val ((res, (timing, reports)), time) = cpu_time "total time" (fn () => invoke_mtd thy t
   353       handle ERROR s => (tracing s; (Error, ([], NONE))))
   354     val _ = Output.urgent_message (" Done")
   355   in (res, (time :: timing, reports)) end
   356 *)  
   357 fun safe_invoke_mtd thy (mtd_name, invoke_mtd) t =
   358   let
   359     val _ = Output.urgent_message ("Invoking " ^ mtd_name)
   360     val (res, timing) = (*cpu_time "total time"
   361       (fn () => *)case try (invoke_mtd thy) t of
   362           SOME (res, timing) => (res, timing)
   363         | NONE => (Output.urgent_message ("**** PROBLEMS WITH " ^ Syntax.string_of_term_global thy t);
   364            (Error, []))
   365     val _ = Output.urgent_message (" Done")
   366   in (res, timing) end
   367 
   368 (* theory -> term list -> mtd -> subentry *)
   369 
   370 fun test_mutants_using_one_method thy mutants (mtd_name, invoke_mtd) =
   371   let
   372      val res = map (fst o safe_invoke_mtd thy (mtd_name, invoke_mtd)) mutants
   373   in
   374     (mtd_name, count GenuineCex res, count PotentialCex res, count NoCex res,
   375      count Donno res, count Timeout res, count Error res)
   376   end
   377 
   378 (* creating entries *)
   379 
   380 fun create_entry thy thm exec mutants mtds =
   381   (Thm.get_name_hint thm, exec, map (test_mutants_using_one_method thy mutants) mtds)
   382 
   383 fun create_detailed_entry thy thm exec mutants mtds =
   384   let
   385     fun create_mutant_subentry mutant = (mutant,
   386       map (fn (mtd_name, invoke_mtd) =>
   387         (mtd_name, safe_invoke_mtd thy (mtd_name, invoke_mtd) mutant)) mtds)
   388   in
   389     (Thm.get_name_hint thm, exec, prop_of thm, map create_mutant_subentry mutants)
   390   end
   391 
   392 (* (theory -> thm -> bool -> term list -> mtd list -> 'a) -> theory -> mtd list -> thm -> 'a *)
   393 fun mutate_theorem create_entry thy mtds thm =
   394   let
   395     val exec = is_executable_thm thy thm
   396     val _ = tracing (if exec then "EXEC" else "NOEXEC")
   397     val mutants =
   398           (if num_mutations = 0 then
   399              [Thm.prop_of thm]
   400            else
   401              Mutabelle.mutate_mix (Thm.prop_of thm) thy comms forbidden
   402                                   num_mutations)
   403              |> tap (fn muts => tracing ("mutants: " ^ string_of_int (length muts)))
   404              |> filter_out is_forbidden_mutant
   405     val mutants =
   406       if exec then
   407         let
   408           val _ = Output.urgent_message ("BEFORE PARTITION OF " ^
   409                             string_of_int (length mutants) ^ " MUTANTS")
   410           val (execs, noexecs) = List.partition (is_executable_term thy) (take_random (20 * max_mutants) mutants)
   411           val _ = tracing ("AFTER PARTITION (" ^ string_of_int (length execs) ^
   412                            " vs " ^ string_of_int (length noexecs) ^ ")")
   413         in
   414           execs @ take_random (Int.max (0, max_mutants - length execs)) noexecs
   415         end
   416       else
   417         mutants
   418     val mutants = mutants
   419           |> map Mutabelle.freeze |> map freezeT
   420 (*          |> filter (not o is_forbidden_mutant) *)
   421           |> map_filter (try (Sign.cert_term thy))
   422           |> filter (is_some o try (Thm.cterm_of thy))
   423           |> filter (is_some o try (Syntax.check_term (Proof_Context.init_global thy)))
   424           |> take_random max_mutants
   425     val _ = map (fn t => Output.urgent_message ("MUTANT: " ^ Syntax.string_of_term_global thy t)) mutants
   426   in
   427     create_entry thy thm exec mutants mtds
   428   end
   429 
   430 (* theory -> mtd list -> thm list -> report *)
   431 val mutate_theorems = map ooo mutate_theorem
   432 
   433 fun string_of_mutant_subentry thy thm_name (t, results) =
   434   "mutant: " ^ Syntax.string_of_term_global thy t ^ "\n" ^
   435   space_implode "; "
   436     (map (fn (mtd_name, (outcome, timing)) => mtd_name ^ ": " ^ string_of_outcome outcome) results) ^
   437   "\n"
   438 
   439 (* string -> string *)
   440 val unyxml = XML.content_of o YXML.parse_body
   441 
   442 fun string_of_mutant_subentry' thy thm_name (t, results) =
   443   let
   444    (* fun string_of_report (Quickcheck.Report {iterations = i, raised_match_errors = e,
   445       satisfied_assms = s, positive_concl_tests = p}) =
   446       "errors: " ^ string_of_int e ^ "; conclusion tests: " ^ string_of_int p
   447     fun string_of_reports NONE = ""
   448       | string_of_reports (SOME reports) =
   449         cat_lines (map (fn (size, [report]) =>
   450           "size " ^ string_of_int size ^ ": " ^ string_of_report report) (rev reports))*)
   451     fun string_of_mtd_result (mtd_name, (outcome, timing)) =
   452       mtd_name ^ ": " ^ string_of_outcome outcome
   453       (*" with time " ^ " (" ^ space_implode "; " (map (fn (s, t) => (s ^ ": " ^ string_of_int t)) timing) ^ ")"*)
   454       (*^ "\n" ^ string_of_reports reports*)
   455   in
   456     "mutant of " ^ thm_name ^ ":\n"
   457     ^ unyxml (Syntax.string_of_term_global thy t) ^ "\n" ^ space_implode "; " (map string_of_mtd_result results)
   458   end
   459 
   460 fun string_of_detailed_entry thy (thm_name, exec, t, mutant_subentries) = 
   461    thm_name ^ " " ^ (if exec then "[exe]" else "[noexe]") ^ ": " ^
   462    Syntax.string_of_term_global thy t ^ "\n" ^                                    
   463    cat_lines (map (string_of_mutant_subentry' thy thm_name) mutant_subentries) ^ "\n"
   464 
   465 fun theoryfile_string_of_mutant_subentry thy thm_name (i, (t, results)) =
   466   "lemma " ^ thm_name ^ "_" ^ string_of_int (i + 1) ^ ":\n" ^
   467   "\"" ^ unyxml (Syntax.string_of_term_global thy t) ^
   468   "\" \nquickcheck\noops\n"
   469 
   470 fun theoryfile_string_of_detailed_entry thy (thm_name, exec, t, mutant_subentries) =
   471   "subsubsection {* mutants of " ^ thm_name ^ " *}\n\n" ^
   472   cat_lines (map_index
   473     (theoryfile_string_of_mutant_subentry thy thm_name) mutant_subentries) ^ "\n"
   474 
   475 (* subentry -> string *)
   476 fun string_for_subentry (mtd_name, genuine_cex, potential_cex, no_cex, donno,
   477                          timeout, error) =
   478   "    " ^ mtd_name ^ ": " ^ string_of_int genuine_cex ^ "+ " ^
   479   string_of_int potential_cex ^ "= " ^ string_of_int no_cex ^ "- " ^
   480   string_of_int donno ^ "? " ^ string_of_int timeout ^ "T " ^
   481   string_of_int error ^ "!"
   482 
   483 (* entry -> string *)
   484 fun string_for_entry (thm_name, exec, subentries) =
   485   thm_name ^ " " ^ (if exec then "[exe]" else "[noexe]") ^ ":\n" ^
   486   cat_lines (map string_for_subentry subentries) ^ "\n"
   487 
   488 (* report -> string *)
   489 fun string_for_report report = cat_lines (map string_for_entry report)
   490 
   491 (* string -> report -> unit *)
   492 fun write_report file_name =
   493   File.write (Path.explode file_name) o string_for_report
   494 
   495 (* theory -> mtd list -> thm list -> string -> unit *)
   496 fun mutate_theorems_and_write_report thy mtds thms file_name =
   497   let
   498     val _ = Output.urgent_message "Starting Mutabelle..."
   499     val ctxt = Proof_Context.init_global thy
   500     val path = Path.explode file_name
   501     (* for normal report: *)
   502     (*
   503     val (gen_create_entry, gen_string_for_entry) = (create_entry, string_for_entry)
   504     *)
   505     (* for detailled report: *)
   506     val (gen_create_entry, gen_string_for_entry) = (create_detailed_entry, string_of_detailed_entry thy)
   507     (* for theory creation: *)
   508     (*val (gen_create_entry, gen_string_for_entry) = (create_detailed_entry, theoryfile_string_of_detailed_entry thy)*)
   509   in
   510     File.write path (
   511     "Mutation options = "  ^
   512       "max_mutants: " ^ string_of_int max_mutants ^
   513       "; num_mutations: " ^ string_of_int num_mutations ^ "\n" ^
   514     "QC options = " ^
   515       (*"quickcheck_generator: " ^ quickcheck_generator ^ ";*)
   516       "size: " ^ string_of_int (Config.get ctxt Quickcheck.size) ^
   517       "; iterations: " ^ string_of_int (Config.get ctxt Quickcheck.iterations) ^ "\n" ^
   518     "Isabelle environment = ISABELLE_GHC: " ^ getenv "ISABELLE_GHC" ^ "\n");
   519     map (File.append path o gen_string_for_entry o mutate_theorem gen_create_entry thy mtds) thms;
   520     ()
   521   end
   522 
   523 end;