0

My story is quite long, and my code for what I actually wanted to do is also quite long. So first I will describe what I have tried with the formula in the title, and then I will describe how I encountered something like this in practice, putting my actual code at the very bottom.

I understand that maybe I should have done what I want to do in an entirely different way: I have no prior experience with Isabelle (or even much experience with functional programming languages like Haskell), so my code is probably non-idiomatic.

What I have done with it

First, I understand that everything in the section can be proved with by presburger (as try told me). That doesn't work with my actual problem, though, and it probably shouldn't, because there I deal with custom datatypes containing integers, instead of integers themselves.

In attempts to reduce this into an minimal complete verifiable example, I noticed that even simple formulas like "∃(x::nat). (x ≠ 2)" are hard to prove for Isabelle. However, I thought something concrete like x = 1 may help. Indeed,

lemma "∃(x::nat). (x ≠ 2 ∧ x = 1)"
  by auto

worked easily, but

lemma "∃(x::nat). (x ≠ 2 ∧ (x ≠ 2 ⟶ x = 1))"
  by auto

still fails. I thought maybe I should put the concrete part first, but when I tried

lemma "∃(x::nat). ((x ≠ 2 ⟶ x = 1) ∧ x ≠ 2)"
  by auto

the by auto line stayed purple in the jEdit, which I believe indicates an infinite loop.

How I encountered it

I am trying to use Isabelle to make a model for Shogi, and ultimately prove the uniqueness of solution for some Shogi problems. When I tried to prove that a simple configuration is a checkmate, Isabelle was stuck at (for context, I had datatype coord = Coord int int):

∃dst. dst ≠ Coord 5 3 ∧
          (dst ≠ Coord 5 3 ⟶
           dst ≠ Coord 5 2 ∧
           (dst ≠ Coord 5 2 ⟶
            (dst = Coord 5 1 ⟶
             (∃src. src ≠ Coord 5 3 ∧ (src ≠ Coord 5 3 ⟶ src = Coord 5 2))) ∧
            dst = Coord 5 1))

I know this looks like a mess. Let me slightly tweak the indent to make it hopefully easier to parse for human eyes.

∃dst. dst ≠ Coord 5 3 ∧
      (
          dst ≠ Coord 5 3 ⟶
          dst ≠ Coord 5 2 ∧
          (
              dst ≠ Coord 5 2 ⟶
              (
                  dst = Coord 5 1 ⟶
                  (∃src. src ≠ Coord 5 3 ∧ (src ≠ Coord 5 3 ⟶ src = Coord 5 2))
              ) ∧ dst = Coord 5 1
          )
      )

Hopefully it can be seen that src = Coord 5 2 satisfies the single line with src, and then dst = Coord 5 1 satisfies the whole thing.

I tried to put this as a lemma and prove it by auto --- of course that doesn't work. I finally tried by try, and it told me:

"cvc4": Try this: by (smt coord.inject) (866 ms)

OK, that worked. But I am really confused because I don't even know what smt is.

Regardless, that method (after applying auto) successfully proved that Player 2 is in check. I tried to use the same method to prove that it's a checkmate (which, at the very least, amounts to proving that in several configurations Player 2 is still in check), but that resulted in an error too:

Solver z3: Solver "z3" failed -- enable tracing using the "smt_trace" option for details

Even sledgehammer didn't help me.

Since checks and checkmates are in the very basic rules of Shogi problems, I think I need to be able to recognize them automatically before I can do anything nontrivial. I am open to having to write down and prove a few lemmas manually, but now I believe that I might be doing something fundamentally wrong.

My code

If there is anything I need to clarify, please ask in the comments.

theory Shogi
  imports Main "HOL-Library.Multiset"
begin

datatype coord = Coord int int
datatype vector = Vector int int
datatype piece_type = King | Gold
datatype move = Move coord coord | Drop piece_type coord
datatype player = Sente | Gote
type_synonym on_board =  "(coord, player * piece_type) map"
datatype board = Board on_board "(player * piece_type) multiset" player

fun opponent :: "player ⇒ player" where
  "opponent Sente = Gote" |
  "opponent Gote = Sente"

fun diff :: "coord ⇒ coord ⇒ vector" where
  "diff (Coord x1 y1) (Coord x0 y0) = Vector (x1 - x0) (y1 - y0)"

fun negate :: "vector ⇒ vector" where
  "negate (Vector x y) = (Vector (-x) (-y))"

fun to_hand :: "(player * piece_type) option ⇒ (player * piece_type) multiset" where
  "to_hand None = {#}" |
  "to_hand (Some (owner, piece)) = {#((opponent owner), piece)#}"

fun make_move :: "board ⇒ move ⇒ board" where
  "make_move (Board on_board in_hand to_move) (Move src dst) =
    (Board
      (on_board(src := None, dst := (on_board src)))
      (in_hand + (to_hand (on_board dst)))
      (opponent to_move)
    )" |
  "make_move (Board on_board in_hand to_move) (Drop piece pos) =
    (Board
      (on_board(pos := Some (to_move, piece)))
      (in_hand - {#(to_move, piece)#})
      (opponent to_move)
    )"

fun is_on_board :: "coord ⇒ bool" where
  "is_on_board (Coord file rank) = (1 ≤ file ∧ file ≤ 9 ∧ 1 ≤ rank ∧ rank ≤ 9)"

fun movement_vector :: "piece_type ⇒ vector ⇒ bool" where
  "movement_vector King (Vector x y) = ((x, y) ∈ {
    (1, -1), (0, -1), (-1, -1),
    (1,  0),          (-1,  0),
    (1,  1), (0,  1), (-1,  1)})" | 
  "movement_vector Gold (Vector x y) = ((x, y) ∈ {
    (1, -1), (0, -1), (-1, -1),
    (1,  0),          (-1,  0),
             (0,  1)          })"

fun negated_movement_vector :: "piece_type ⇒ vector ⇒ bool" where
  "negated_movement_vector piece vector = movement_vector piece (negate vector)"

fun absolute_movement_vector :: "player ⇒ piece_type ⇒ vector ⇒ bool" where
  "absolute_movement_vector Sente = movement_vector" |
  "absolute_movement_vector Gote = negated_movement_vector"

fun is_legal_for_piece :: "(player * piece_type) option ⇒ player ⇒ on_board
                                                        ⇒ coord ⇒ coord ⇒ bool" where
  "is_legal_for_piece None _ _ _ _ = False" |
  "is_legal_for_piece (Some (owner, piece)) to_move on_board src dst =
    (owner = to_move ∧ absolute_movement_vector owner piece (diff dst src))"

fun get_owner :: "(player * piece_type) option ⇒ player option" where
  "get_owner None = None" | "get_owner (Some (owner, piece)) = Some owner"

fun is_legal_physically :: "board ⇒ move ⇒ bool" where
  "is_legal_physically (Board on_board in_hand to_move) (Move src dst) =
    (
      (is_on_board dst) ∧
      (get_owner (on_board dst)) ≠ Some to_move ∧
      (is_legal_for_piece (on_board src) to_move on_board src dst)
    )" |
  "is_legal_physically (Board on_board in_hand to_move) (Drop piece pos) =
    ((is_on_board pos) ∧ ((on_board pos) = None))"

fun is_in_check :: "on_board ⇒ player ⇒ bool" where
  "is_in_check on_board player = 
    (∃dst src.
      (on_board dst) = Some (player, King) ∧
      (is_legal_for_piece (on_board src) (opponent player) on_board src dst)
    )"

fun is_legal_board :: "board ⇒ bool" where
  "is_legal_board (Board on_board in_hand to_move) = 
    (¬(is_in_check on_board (opponent to_move)))"

fun is_legal :: "board ⇒ move ⇒ bool" where
  "is_legal board move = 
    (
      (is_legal_physically board move) ∧ 
      (is_legal_board (make_move board move))
    )"

fun is_mate :: "board ⇒ bool" where "is_mate board = (¬(∃move. (is_legal board move)))"

fun is_checkmate :: "board ⇒ bool" where
  "is_checkmate (Board on_board in_hand to_move) = (
    (is_mate (Board on_board in_hand to_move)) ∧ (is_in_check on_board to_move))"

abbreviation board1 :: on_board where "board1 ≡ (map_of [
    ((Coord 5 1), (Gote, King)),
    ((Coord 5 2), (Sente, Gold)),
    ((Coord 5 3), (Sente, King))
  ])"

lemma "∃dst. dst ≠ Coord 5 3 ∧
          (dst ≠ Coord 5 3 ⟶
           dst ≠ Coord 5 2 ∧
           (dst ≠ Coord 5 2 ⟶
            (dst = Coord 5 1 ⟶
             (∃src. src ≠ Coord 5 3 ∧ (src ≠ Coord 5 3 ⟶ src = Coord 5 2))) ∧
            dst = Coord 5 1))"
  by (smt coord.inject)

lemma "is_in_check board1 Gote"
  apply auto
  apply (smt coord.inject)
  done

lemma "is_checkmate (Board board1 {#} Gote)"
  apply auto
  apply sledgehammer (* Both "cvc4" and "vampire" timed out *)
  apply (smt coord.inject) (* Solver z3: Solver "z3" failed -- enable tracing using the "smt_trace" option for details *)
  apply presburger (* Failed to apply proof method *)

end
Community
  • 1
  • 1
Imperishable Night
  • 1,503
  • 9
  • 19
  • I don't have time to write a full answer to your various points. But a first hint: In your `is_checkmate` proof, `auto` generates *two* subgoals. If you use `defer apply (smt coord.inject)` after `auto`, you can actually solve the subgoal you discussed here. Sledgehammer fails on the second subgoal. `⋀x. is_legal_physically ... ⟹ is_legal_board (make_move (Board [...] {#} Gote) x) ⟹ False`. (In general, `auto` should rather be used to finish a proof than to begin it.) – Ben Keks Jun 25 '19 at 17:52
  • Oh, I see, `defer` and `prefer` are how to move subgoals around. I will try more after work today. There seems to be so much to learn... – Imperishable Night Jun 25 '19 at 18:19
  • @ImperishableNight Depending on where you are in the process of learning Isabelle, the information that I am about to provide might seem trivial. I merely thought that in your solution attempts you did not mention whether you tried to exhibit the object for your existence proofs. For example, `lemma "is_in_check board1 Gote" unfolding is_in_check_def opponent.simps proof(intro exI, intro conjI) show "board1 (Coord 5 1) = Some (Gote, King)" by simp show "is_legal_for_piece (board1 (Coord 5 2)) Sente board1 (Coord 5 2) (Coord 5 1)" by auto qed ` – user9716869 - supports Ukraine Jun 27 '19 at 20:46
  • @xanonec It's definitely helpful! That seems much easier to understand and probably more correct than calling some `smt` prover. – Imperishable Night Jun 27 '19 at 20:54
  • @ImperishableNight Indeed, from https://proofcraft.org/blog/isabelle-style.html: "Do not use smt in proofs you need to maintain over longer time. The outcome of the smt command depends on tools external to Isabelle, so it can be hard to predict if they will prove the same things in the future or if they will even still be available in an Isabelle-compatible form in a number of years." – user9716869 - supports Ukraine Jun 27 '19 at 21:08
  • @ImperishableNight Quite frankly, I think that Isabelle may still be best viewed, primarily, as a tool for proof formalisation and, much less, for proof automation (of course, its capabilities for proof automation are also very impressive). I always found it helpful to write the proof down on the paper before trying to formalise it. I believe that, in most cases, the perfect question for this forum should contain a pen-and-paper outline of the proof that one is trying to formalise. Otherwise, the question may be best suited for MSE. – user9716869 - supports Ukraine Jun 27 '19 at 21:13
  • @xanonec Indeed, I am not expecting Isabelle to automatically prove the facts I ultimately want to prove. It is just that in the context of chess problems, facts like checkmate positions are seen as "trivial", so I want to automate that part as much as possible. – Imperishable Night Jun 27 '19 at 21:16
  • @ImperishableNight If a proof is trivial, then it should be very easy to write its outline. Once this is done, it should be very easy to formalise it :). Once you formalise several similar trivial proofs, you should also be able to see some kind of pattern that you will be able to exploit to aid in the automation of other proofs that are similar. – user9716869 - supports Ukraine Jun 27 '19 at 21:32
  • @xanonec True! Now that I have a clearer idea how Isabelle/Isar proofs work, I have some ideas what auxiliary propositions I need to do that. If I run into further problems, I think I will open a new question. Thank you! Incidentally... what is MSE? – Imperishable Night Jun 27 '19 at 21:43
  • @ImperishableNight Mathematics Stack Exchange: https://math.stackexchange.com – user9716869 - supports Ukraine Jun 27 '19 at 22:13
  • @xanonec I see. I thought you were mentioning a tool that allowed more automation in searching for a proof (in particular the brute-forcy parts) :/ – Imperishable Night Jun 27 '19 at 22:28

0 Answers0