Zulip Chat Archive

Stream: general

Topic: Automatic proof for nat-sub


Damiano Testa (Sep 07 2022 at 17:05):

Dear All,

have you ever been annoyed by nat-subtraction?

This post is in the intersection of Proofs that should be automatic and linarith failure.

I tried writing a simple-minded tactic that would split nat-subtractions in a goal into the two obvious cases and continue until all nat-subtractions have been removed.

Below is the experimental tactic and the example showing that it works in the case that @Michael Stoll mentioned in his post.

Feel free to experiment and give comments! If you like it, I can polish it and make a PR.

import tactic.linarith

lemma nat.le_cases (a b : ) : a - b = 0   c, a = b + c :=
begin
  by_cases ab : a  b,
  { exact or.inl (tsub_eq_zero_of_le ab) },
  { refine or.inr _,
    rcases nat.exists_eq_add_of_le ((not_le.mp ab).le) with c, rfl⟩,
    exact _, rfl },
end

namespace tactic

/--  `get_sub e` extracts a list of pairs `(a, b)` from the expression `e`, where `a - b` is a
subexpression of `e`. -/
meta def get_sub : expr  tactic (list (expr × expr))
| `(%%a - %%b) := do [ga, gb]  [a, b].mmap get_sub,
                      return ((a, b) :: (ga ++ gb))
| (expr.app f a) := do [gf, ga]  [f, a].mmap get_sub,
                      return (gf ++ ga)
| _ := pure []

/--  `remove_one_sub e` scans the expression `e` and selects the last occurrence of a
subtractiion `a - b`.  It splits two cases:
*  `a ≤ b`, in which case it replaces `a - b` with `0`, introduces the inequality `a ≤ b` into
   the local context and continues from there;
*  writes `a = b + c`, for some `c`, tries to substitute this equality and continues from there.
-/
meta def remove_one_sub (e : expr) : tactic unit :=
do l  get_sub e,
  match l.last' with
  | none := fail"no subtractions"
  | some (a, b) := do
    -- introduce names with the following meanings:
    -- `eq0 : a - b = 0`, `exis : ∃ c, b + c = a`, `var = c`, `ide : b + c = a`
    [eq0, exis, var, ide]  ["h", "k", "x", "hx"].mmap (λ y, get_unused_name y),
    -- either `a ≤ b` or `a = b + c`
    cases `(nat.le_cases %%a %%b) [eq0, exis],
    -- on the branch where `a ≤ b`...
      prf  get_local eq0,
      rewrite_target prf,
      preq  to_expr ``(nat.sub_eq_zero_iff_le),
      rewrite_hyp preq prf,
      try `[ simp ],
    -- on the branch where `b < a`...
      swap,
      prf  get_local exis,
      cases prf [var, ide],
      prf  get_local ide,
      -- either substitute or rewrite
      subst prf <|> rewrite_target prf,
      `[ rw nat.add_sub_cancel_left ] <|> fail"could not rewrite: something went wrong",
      try `[ simp ]
  end

namespace interactive

/--  Iterate replacing a subtraction with two cases.  Once this is done, try `linarith` on
all remaining goals. -/
meta def remove_subs : tactic unit :=
do iterate_at_most' 10 $ target >>= remove_one_sub,
  any_goals' $ try $ `[ linarith ]

end interactive

end tactic

example {i l : } : 1  i  i + 1  l  i - 1 + (l - i - 1 + 1) + 1 = l :=
begin
  intros,
  remove_subs,
end

Damiano Testa (Sep 07 2022 at 17:26):

Of course, I would be happy to know situations where this tactic works, but it would probably be more useful to have examples where it does not work!

Michael Stoll (Sep 07 2022 at 17:26):

You could remove the spurious d in the example :smile:

Damiano Testa (Sep 07 2022 at 17:27):

Done! I edited the code above.

Andrew Yang (Sep 07 2022 at 17:31):

I think it would be useful to have a version without simps and linariths, so that it could be used mid-proof without introducing non-terminal simps.

Damiano Testa (Sep 07 2022 at 17:35):

Andrew, good point! If I end up making a PR, I'll definitely make sure not to introduce non-terminal simps!

Damiano Testa (Sep 07 2022 at 17:37):

Btw, the simp there is mostly a lazy way of dealing with subtractions of the form 0 - x.

Damiano Testa (Sep 07 2022 at 21:48):

I created #16422.

The tactic no longer uses simp and will only use linarith if it is called via remove_subs!.

Damiano Testa (Sep 09 2022 at 14:28):

Dear All,

I realize that merging a tactic this close to the transition to Lean4 is not anyone's priority.

Still, if someone could take a look at #16443 and give me some comments, I would be very grateful! My main motivation is that I have been trying to do some Lean4 programming, but I still find it easier to go via Lean3 code.

Thus, even if this tactic does not get merged, I could use a Lean3-mergeable version as a canvas to port it to Lean4.

Thanks!

Damiano Testa (Sep 12 2022 at 02:08):

Following up on the discussion here, I made the small changes to make remove_subs to work with tsub.

However, while the tactic does work, linarith seems to be much less developed for canonically_ordered... than for nat.

Damiano Testa (Sep 12 2022 at 08:56):

Here are explicit examples highlighting the difference in behaviour of linarith:

import data.real.nnreal
open_locale nnreal

example {a : } (h : a = 0) : a = 0 :=
by linarith  -- succeeds

example {R} [canonically_linear_ordered_semifield R] {a : R} (h : a = 0) : a = 0 :=
by linarith  -- fails

example {a : 0} (h : a = 0) : a = 0 :=
by linarith  -- fails

Thus, if the truncated subtractions that remove_subs is going to eliminate are not in nat, then calling remove_subs! may be equivalent (but slower) to calling remove_subs.


Last updated: Dec 20 2023 at 11:08 UTC