CookBook/Recipes/ExternalSolver.thy
changeset 94 531e453c9d67
parent 83 0fb5f91d5109
child 102 5e309df58557
equal deleted inserted replaced
93:62fb91f86908 94:531e453c9d67
     1 theory ExternalSolver
     1 theory ExternalSolver
     2 imports "../Base"
     2 imports "../Base"
       
     3 uses ("external_solver.ML")
     3 begin
     4 begin
     4 
     5 
     5 section {* Using an External Solver\label{rec:external} *} 
     6 
       
     7 section {* Executing an External Application *}
     6 
     8 
     7 text {*
     9 text {*
     8   {\bf Problem:}
    10   {\bf Problem:}
     9   You want to use an external solver, because the solver might be more efficient 
    11   You want to use an external application.
    10   for deciding a certain class of formulae than Isabelle tactics.
    12   \smallskip
    11   \smallskip
    13 
    12 
    14   {\bf Solution:} The function @{ML system_out} might be the right thing for
    13   {\bf Solution:} The easiest way to do this is by implementing an oracle.
    15   you.
    14   We will also construct proofs inside Isabelle by using the results produced 
    16   \smallskip
    15   by the oracle.
    17 
       
    18   This function executes an external command as if printed in a shell. It
       
    19   returns the output of the program and its return value.
       
    20 
       
    21   For example, consider running an ordinary shell commands:
       
    22 
       
    23   @{ML_response [display,gray] 
       
    24     "system_out \"echo Hello world!\"" "(\"Hello world!\\n\", 0)"}
       
    25 
       
    26   Note that it works also fine with timeouts (see Recipe~\ref{rec:timeout}
       
    27   on Page~\pageref{rec:timeout}), i.e. external applications are killed
       
    28   properly. For example, the following expression takes only approximately
       
    29   one second:
       
    30 
       
    31   @{ML_response [display,gray] 
       
    32     "TimeLimit.timeLimit (Time.fromSeconds 1) system_out \"sleep 30\"
       
    33      handle TimeLimit.TimeOut => (\"timeout\", ~1)" "(\"timeout\", ~1)"}
       
    34 *}
       
    35 
       
    36 text {*
       
    37   The function @{ML system_out} can also be used for more reasonable
       
    38   applications, e.g. coupling external solvers with Isabelle. In that case,
       
    39   one has to make sure that Isabelle can find the particular executable.
       
    40   One way to ensure this is by adding a Bash-like variable binding into
       
    41   one of Isabelle's settings file (prefer the user settings file usually to
       
    42   be found at @{text "$HOME/.isabelle/etc/settings"}).
       
    43   
       
    44   For example, assume you want to use the application @{text foo} which
       
    45   is here supposed to be located at @{text "/usr/local/bin/"}.
       
    46   The following line has to be added to one of Isabelle's settings file:
       
    47 
       
    48   @{text "FOO=/usr/local/bin/foo"}
       
    49 
       
    50   In Isabelle, this application may now be executed by
       
    51 
       
    52   @{ML_response_fake [display,gray] "system_out \"$FOO\"" "\<dots>"}
       
    53 *}
       
    54 
       
    55 
       
    56 
       
    57 
       
    58 
       
    59 
       
    60 section {* Writing an Oracle\label{rec:external} *} 
       
    61 
       
    62 text {*
       
    63   {\bf Problem:}
       
    64   You want to use a fast, new decision procedure not based one Isabelle's
       
    65   tactics, and you do not care whether it is sound.
       
    66   \smallskip
       
    67 
       
    68   {\bf Solution:} Isabelle provides the oracle mechanisms to bypass the
       
    69   inference kernel. Note that theorems proven by an oracle carry a special
       
    70   mark to inform the user of their potential incorrectness.
    16   \smallskip
    71   \smallskip
    17 
    72 
    18   \begin{readmore}
    73   \begin{readmore}
    19   A short introduction to oracles can be found in [isar-ref: no suitable label
    74   A short introduction to oracles can be found in [isar-ref: no suitable label
    20   for section 3.11]. A simple example is given in 
    75   for section 3.11]. A simple example, which we will slightly extend here,
    21   @{ML_file "FOL/ex/IffOracle"}.
    76   is given in @{ML_file "FOL/ex/IffOracle"}. The raw interface for adding
    22   (TODO: add more references to the code)
    77   oracles is @{ML add_oracle in Thm} in @{ML_file "Pure/thm"}.
    23   \end{readmore}
    78   \end{readmore}
    24 
    79 
    25   For our explanation here, we will use the @{text metis} prover for proving
    80   For our explanation here, we restrict ourselves to decide propositional
    26   propositional formulae. The general method will be roughly as follows: 
    81   formulae which consist only of equivalences between propositional variables,
    27   Given a goal @{text G}, we transform it into the syntactical respresentation 
    82   i.e. we want to decide whether @{term "P = (Q = P) = Q"} is a tautology.
    28   of @{text "metis"}, build the CNF of the negated formula and then let metis search 
    83 
    29   for a refutation. @{text Metis} will either return the proved goal or raise 
    84   Assume, that we have a decision procedure for such formulae, implemented
    30   an exception meaning 
    85   in ML. Since we do not care how it works, we will use it here as an
    31   that it was unable to prove the goal (FIXME: is this so?).
    86   ``external solver'':
    32 
    87 *}
    33   The translation function from Isabelle propositions into formulae of 
    88 
    34   @{text metis} is as follows:
    89 use "external_solver.ML"
       
    90 
       
    91 text {*
       
    92   We do, however, know that the solver provides a function
       
    93   @{ML IffSolver.decide}.
       
    94   It takes a string representation of a formula and returns either
       
    95   @{ML true} if the formula is a tautology or
       
    96   @{ML false} otherwise. The input syntax is specified as follows:
       
    97 
       
    98   formula $::=$ atom $\mid$ \verb|(| formula \verb|<=>| formula \verb|)|
       
    99 
       
   100   and all token are separated by at least one space.
       
   101 
       
   102   (FIXME: is there a better way for describing the syntax?)
       
   103  
       
   104   We will proceed in the following way. We start by translating a HOL formula
       
   105   into the string representation expected by the solver. The solver's result
       
   106   is then used to build an oracle, which we will subsequently use as a core
       
   107   for an Isar method to be able to apply the oracle in proving theorems.
       
   108 
       
   109   Let us start with the translation function from Isabelle propositions into
       
   110   the solver's string representation. To increase efficiency while building
       
   111   the string, we use functions from the @{text Buffer} module.
    35   *}
   112   *}
    36 
   113 
    37 ML{*fun trans t =
   114 ML {*fun translate t =
    38   (case t of
   115   let
    39     @{term Trueprop} $ t => trans t
   116     fun trans t =
    40   | @{term True} => Metis.Formula.True
   117       (case t of
    41   | @{term False} => Metis.Formula.False
   118         @{term "op = :: bool \<Rightarrow> bool \<Rightarrow> bool"} $ t $ u =>
    42   | @{term Not} $ t => Metis.Formula.Not (trans t)
   119           Buffer.add " (" #>
    43   | @{term "op &"} $ t1 $ t2 => Metis.Formula.And (trans t1, trans t2)
   120           trans t #>
    44   | @{term "op |"} $ t1 $ t2 => Metis.Formula.Or (trans t1, trans t2)
   121           Buffer.add "<=>" #> 
    45   | @{term "op -->"} $ t1 $ t2 => Metis.Formula.Imp (trans t1, trans t2)
   122           trans u #>
    46   | @{term "op = :: bool => bool => bool"} $ t1 $ t2 => 
   123           Buffer.add ") "
    47                                      Metis.Formula.Iff (trans t1, trans t2)
   124       | Free (n, @{typ bool}) =>
    48   | Free (n, @{typ bool}) => Metis.Formula.Atom (n, [])
   125          Buffer.add " " #> 
    49   | _ => error "inacceptable term")*}
   126          Buffer.add n #>
    50 
   127          Buffer.add " "
       
   128       | _ => error "inacceptable term")
       
   129   in Buffer.content (trans t Buffer.empty) end
       
   130 *}
       
   131 
       
   132 text {*
       
   133   Here is the string representation of the term @{term "p = (q = p)"}:
       
   134 
       
   135   @{ML_response 
       
   136     "translate @{term \"p = (q = p)\"}" 
       
   137     "\" ( p <=> ( q <=> p ) ) \""}
       
   138 
       
   139   Let us check, what the solver returns when given a tautology:
       
   140 
       
   141   @{ML_response 
       
   142     "IffSolver.decide (translate @{term \"p = (q = p) = q\"})"
       
   143     "true"}
       
   144 
       
   145   And here is what it returns for a formula which is not valid:
       
   146 
       
   147   @{ML_response 
       
   148     "IffSolver.decide (translate @{term \"p = (q = p)\"})" 
       
   149     "false"}
       
   150 *}
    51 
   151 
    52 text {* 
   152 text {* 
    53   An example is as follows:
   153   Now, we combine these functions into an oracle. In general, an oracle may
    54 
   154   be given any input, but it has to return a certified proposition (a
    55 @{ML_response [display,gray] "trans @{prop \"A \<and> B\"}" 
   155   special term which is type-checked), out of which Isabelle's inference
    56 "Metis.Formula.And 
   156   kernel ``magically'' makes a theorem.
    57         (Metis.Formula.Atom (\"A\", []), Metis.Formula.Atom (\"B\", []))"}
   157 
    58 
   158   Here, we take the proposition to be show as input. Note that we have
    59 
   159   to first extract the term which is then passed to the translation and
    60   The next function computes the conjunctive-normal-form.
   160   decision procedure. If the solver finds this term to be valid, we return
    61 *}
   161   the given proposition unchanged to be turned then into a theorem:
    62 
   162 *}
    63 
   163 
    64 ML %linenumbers{*fun make_cnfs fm =
   164 oracle iff_oracle = {* fn ct =>
    65        fm |> Metis.Formula.Not
   165   if IffSolver.decide (translate (HOLogic.dest_Trueprop (Thm.term_of ct)))
    66           |> Metis.Normalize.cnf
   166   then ct
    67           |> map Metis.Formula.stripConj
   167   else error "Proof failed."*}
    68           |> map (map Metis.Formula.stripDisj)
   168 
    69           |> map (map (map Metis.Literal.fromFormula))
   169 text {*
    70           |> map (map Metis.LiteralSet.fromList)
   170   Here is what we get when applying the oracle:
    71           |> map (map Metis.Thm.axiom)*}
   171 
    72 
   172   @{ML_response_fake "iff_oracle @{cprop \"p = (p::bool)\"}" "p = p"}
    73 text {* 
   173 
    74   (FIXME: Is there a deep reason why Metis.Normalize.cnf returns a list?)
   174   (FIXME: is there a better way to present the theorem?)
    75 
   175 
    76   (FIXME: What does Line 8 do?)
   176   To be able to use our oracle for Isar proofs, we wrap it into a tactic:
    77 
   177 *}
    78   (FIXME: Can this code be improved?)
   178 
    79 
   179 ML{*val iff_oracle_tac =
    80 
   180   CSUBGOAL (fn (goal, i) => 
    81   Setting up the resolution.
   181     (case try iff_oracle goal of
    82 *}
   182       NONE => no_tac
    83 
   183     | SOME thm => rtac thm i))*}
    84 ML{*fun refute cls =
   184 
    85  let val result = 
   185 text {*
    86         Metis.Resolution.loop             
   186   and create a new method solely based on this tactic:
    87           (Metis.Resolution.new Metis.Resolution.default cls)
   187 *}
    88  in
   188 
    89    (case result of
   189 method_setup iff_oracle = {*
    90       Metis.Resolution.Contradiction _ => true
   190    Method.no_args (Method.SIMPLE_METHOD' iff_oracle_tac)
    91     | Metis.Resolution.Satisfiable _ => false)
   191 *} "Oracle-based decision procedure for chains of equivalences"
    92  end*}
   192 
    93 
   193 text {*
    94 text {* 
   194   (FIXME: what does @{ML "Method.SIMPLE_METHOD'"} do? ... what do you mean?)
    95   Stringing the functions together.
   195 
    96 *}
   196   Finally, we can test our oracle to prove some theorems:
    97 
   197 *}
    98 ML{*fun solve f = List.all refute (make_cnfs f)*}
   198 
    99 
   199 lemma "p = (p::bool)"
   100 text {* Setting up the oracle*}
   200    by iff_oracle
   101 
   201 
   102 ML{*fun prop_dp (thy, t) = 
   202 lemma "p = (q = p) = q"
   103         if solve (trans t) then (Thm.cterm_of thy t) 
   203    by iff_oracle
   104         else error "Proof failed."*}
   204 
   105 
   205 
   106 oracle prop_oracle = prop_dp
   206 text {*
   107 
   207 (FIXME: say something about what the proof of the oracle is ... what do you mean?)
   108 text {* (FIXME: What does oracle do?) *}
       
   109 
       
   110 
       
   111 ML{*fun prop_oracle_tac ctxt = 
       
   112   SUBGOAL (fn (goal, i) => 
       
   113     (case (try prop_oracle (ProofContext.theory_of ctxt, goal)) of
       
   114       SOME thm => rtac thm i
       
   115     | NONE => no_tac))*}
       
   116 
       
   117 text {*
       
   118   (FIXME: The oracle returns a @{text cterm}. How is it possible
       
   119   that I can apply this cterm with @{ML rtac}?)
       
   120 *}
       
   121 
       
   122 method_setup prop_oracle = {*
       
   123   Method.ctxt_args (fn ctxt => Method.SIMPLE_METHOD' (prop_oracle_tac ctxt))
       
   124 *} "Oracle-based decision procedure for propositional logic"
       
   125 
       
   126 text {* (FIXME What does @{ML Method.SIMPLE_METHOD'} do?) *}
       
   127 
       
   128 lemma test: "p \<or> \<not>p"
       
   129   by prop_oracle
       
   130 
       
   131 text {* (FIXME: say something about what the proof of the oracle is)
       
   132 
       
   133   @{ML_response_fake [display,gray] "Thm.proof_of @{thm test}" "???"}
       
   134 
       
   135 *} 
   208 *} 
   136 
   209 
   137 
   210 
   138 
       
   139 lemma "((p \<longrightarrow> q) \<longrightarrow> p) \<longrightarrow> p"
       
   140   by prop_oracle
       
   141 
       
   142 lemma "\<forall>x::nat. x \<ge> 0"
       
   143   sorry
       
   144 
       
   145 text {*
       
   146   (FIXME: proof reconstruction)
       
   147 *}
       
   148 
       
   149 
       
   150 
       
   151 text {*
       
   152   For communication with external programs, there are the primitives
       
   153   @{ML_text system} and @{ML_text system_out}, the latter of which captures
       
   154   the invoked program's output. For simplicity, here, we will use metis, an
       
   155   external solver included in the Isabelle destribution. Since it is written
       
   156   in ML, we can call it directly without the detour of invoking an external
       
   157   program.
       
   158 *}
       
   159 
       
   160 
       
   161 end
   211 end