Lean 4 自动化内幕

19. pushneg、contrapose、bycontra🔗

本章目标:掌握三个逻辑变换 tactic 的工作原理、适用场景与失败模式, 学会在分析学证明中正确选择和组合它们。

19.1. 18.1 问题:否定与逆否的手工拆解🔗

分析学中充斥着"否定一个带量词的命题"的需求。 例如证明某函数不连续,需要否定 ε-δ 定义:

¬(∀ ε > 0, ∃ δ > 0, ∀ x, |x - a| < δ → |f x - f a| < ε)

手工展开需要反复应用 De Morgan 律和量词否定, 同时把 ¬(a < b) 翻译成 b ≤ a——枯燥且容易出错。

push_negcontraposeby_contra 为三类场景设计:

  • push_neg:把 ¬ 向内推到原子公式

  • contrapose:把 P → Q 变为 ¬Q → ¬P

  • by_contra:假设目标的否定,转为推导矛盾

19.2. 18.2 push_neg:否定下推🔗

19.2.1. 基本行为🔗

push_neg 对目标或假设中的否定做 De Morgan 展开, 直到否定落在原子公式上:

example (f : ) (a : ) : ¬( ε > 0, δ > 0, x, |x - a| < δ |f x - f a| < ε) ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| := f: a:(¬ ε > 0, δ > 0, (x : ), |x - a| < δ |f x - f a| < ε) ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| f: a:(¬ ε > 0, δ > 0, (x : ), |x - a| < δ |f x - f a| < ε) ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a|f: a:(∃ ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a|) ¬ ε > 0, δ > 0, (x : ), |x - a| < δ |f x - f a| < ε f: a:(¬ ε > 0, δ > 0, (x : ), |x - a| < δ |f x - f a| < ε) ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| f: a:h:¬ ε > 0, δ > 0, (x : ), |x - a| < δ |f x - f a| < ε ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| `push_neg` has been deprecated. Prefer using `push Not` instead. If you'd rather continue using `push_neg` in your project, you can implement it as follows: ``` open Lean.Parser.Tactic in macro "push_neg" cfg:optConfig loc:(location)? : tactic => `(tactic| push $cfg:optConfig Not $[$loc]?) ``` f: a:h: ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| All goals completed! 🐙 f: a:(∃ ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a|) ¬ ε > 0, δ > 0, (x : ), |x - a| < δ |f x - f a| < ε f: a:h: ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a|¬ ε > 0, δ > 0, (x : ), |x - a| < δ |f x - f a| < ε `push_neg` has been deprecated. Prefer using `push Not` instead. If you'd rather continue using `push_neg` in your project, you can implement it as follows: ``` open Lean.Parser.Tactic in macro "push_neg" cfg:optConfig loc:(location)? : tactic => `(tactic| push $cfg:optConfig Not $[$loc]?) ``` f: a:h: ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| ε > 0, δ > 0, x, |x - a| < δ ε |f x - f a| All goals completed! 🐙

变换规则一览:

  • ¬(P ∧ Q)¬P ∨ ¬Q

  • ¬(P ∨ Q)¬P ∧ ¬Q

  • ¬(∀ x, P x)∃ x, ¬P x

  • ¬(∃ x, P x)∀ x, ¬P x

  • ¬(a ≤ b)b < a

  • ¬(a < b)b ≤ a

  • ¬¬PP(需要 Classical)

19.2.2. 内部机制🔗

push_neg 是一个特化的 simp,使用 @[push_neg] 引理集:

-- Mathlib 核心引理(简化)
@[push_neg] theorem not_and_or : ¬(P ∧ Q) ↔ ¬P ∨ ¬Q   -- ❶ De Morgan
@[push_neg] theorem not_or     : ¬(P ∨ Q) ↔ ¬P ∧ ¬Q   -- ❷ De Morgan
@[push_neg] theorem not_forall : (¬∀ x, P x) ↔ ∃ x, ¬P x  -- ❸ 量词翻转
@[push_neg] theorem not_exists : (¬∃ x, P x) ↔ ∀ x, ¬P x  -- ❹ 量词翻转
@[push_neg] theorem not_le     : ¬(a ≤ b) ↔ b < a     -- ❺ 序关系

能力完全由引理集决定——自定义类型需注册 @[push_neg] 引理。

作用位置:push_neg(目标)、push_neg at h(假设)、 push_neg at h ⊢(两者)、push_neg at *(全部)。

19.3. 18.3 contrapose:逆否变换🔗

当目标形如 P → Q 时,contrapose 将其变为 ¬Q → ¬P

example (a b : ) : a b b 0 a 0 := a:b:a b b 0 a 0 a:b:hdvd:a bb 0 a 0 a:b:hdvd:a ba = 0 b = 0 a:b:hdvd:a bha:a = 0b = 0 a:b:ha:a = 0hdvd:b = 0b = 0 All goals completed! 🐙

19.3.1. contrapose! 变体🔗

contrapose! = contrapose + push_neg, 否定中含 <、量词时特别有用:

example (n : ) : n < 3 n ^ 2 < 9 := n:n < 3 n ^ 2 < 9 n:9 n ^ 2 3 n n:h:9 n ^ 23 n All goals completed! 🐙

如果 context 中有假设 h : P 且目标是 Qcontrapose h 会把 h 消耗并变为 ¬Q → ¬P

19.4. 18.4 by_contra:反证法🔗

by_contra 假设目标的否定,将目标变为 False

example : n : , m, m > n := (n : ), m, m > n h: n, (m : ), m nFalse n:hn: (m : ), m nFalse n:hn: (m : ), m nthis:n + 1 nFalse All goals completed! 🐙

19.4.1. 与 Classical.em 的关系🔗

by_contra 依赖排中律——本质上等价于:

rcases Classical.em P with h | h
· exact h              -- ❶ 若 P 成立,直接用
· exfalso; ...         -- ❷ 若 ¬P,推矛盾

因此 by_contra非构造性的Decidable 实例可用时 Lean 会用可判定版本代替。

19.5. 18.5 组合模式🔗

19.5.1. 模式 A:push_neg 消除否定前缀🔗

example (a : ) (L : ) (h : ε > 0, N, n N, |a n - L| < ε) : ¬( ε > 0, N, n N, ε |a n - L|) := a: L:h: ε > 0, N, n N, |a n - L| < ε¬ ε > 0, (N : ), n N, ε |a n - L| `push_neg` has been deprecated. Prefer using `push Not` instead. If you'd rather continue using `push_neg` in your project, you can implement it as follows: ``` open Lean.Parser.Tactic in macro "push_neg" cfg:optConfig loc:(location)? : tactic => `(tactic| push $cfg:optConfig Not $[$loc]?) ``` a: L:h: ε > 0, N, n N, |a n - L| < ε ε > 0, N, n N, |a n - L| < ε All goals completed! 🐙

19.5.2. 模式 B:contrapose! + 算术🔗

example (f : ) (hf : StrictMono f) (a b : ) : f a = f b a = b := f: hf:StrictMono fa:b:f a = f b a = b f: hf:StrictMono fa:b:a b f a f b All goals completed! 🐙

19.5.3. 模式 C:bycontra + pushneg + 分步推理🔗

example (a b : ) (h : ε > 0, a b + ε) : a b := a:b:h: ε > 0, a b + εa b a:b:h: ε > 0, a b + εh':¬a bFalse `push_neg` has been deprecated. Prefer using `push Not` instead. If you'd rather continue using `push_neg` in your project, you can implement it as follows: ``` open Lean.Parser.Tactic in macro "push_neg" cfg:optConfig loc:(location)? : tactic => `(tactic| push $cfg:optConfig Not $[$loc]?) ``` a:b:h: ε > 0, a b + εh':b < aFalse have := h ((a - b) / 2) (a:b:h: ε > 0, a b + εh':b < a(a - b) / 2 > 0 All goals completed! 🐙) All goals completed! 🐙

19.5.4. 模式 D:by_contra! + obtain + omega🔗

example (n : ) (h : n ^ 2 < 2 * n) : n < 2 := n:h:n ^ 2 < 2 * nn < 2 n:h:n ^ 2 < 2 * nh':2 nFalse All goals completed! 🐙

19.6. 18.6 判断:何时用哪个🔗

目标含 ¬ 或需要否定展开?
  ├─ 是 → push_neg(或 push_neg at h)
  └─ 否 → 目标形如 P → Q?
           ├─ 是,且逆否更自然 → contrapose / contrapose!
           └─ 否,或正面证明困难 → by_contra / by_contra!

经验法则

  • 否定展开后直接匹配假设 → push_neg

  • 逆否方向更容易 → contrapose

  • 需要制造矛盾 → by_contra

  • ! 的变体在含 /</量词时省去手动 push_neg

不该用的场景

  • 目标有直接证明路径——不要为了"保险"加 by_contra

  • by_contra 引入的 ¬P 无法被利用——说明应该走直接证明

19.7. 18.7 失败模式🔗

19.7.1. 失败 1:push_neg 遇到无注册引理的类型🔗

example (a b : MyOrder) : ¬(a ≤ₘ b) := by
  push_neg   -- ❌ 目标不变——不知道 ¬(≤ₘ) 等于什么

诊断push_neg 不报错但目标不变。 修复:注册 @[push_neg] theorem not_myLe : ¬(a ≤ₘ b) ↔ b <ₘ a

19.7.2. 失败 2:contrapose 的目标不是蕴含🔗

example : P ∧ Q := by
  contrapose   -- ❌ goal is not an implication

修复:用 by_contra 代替。

19.7.3. 失败 3:contrapose 对嵌套蕴含的意外行为🔗

example : P → Q → R := by
  contrapose    -- 对整个 (P → Q → R) 做逆否,变为 ¬R → ¬(P → Q)

修复:先 intro 到需要的位置,再 contrapose

19.7.4. 失败 4:push_neg 不处理 ↔ 的否定🔗

example : ¬(P ↔ Q) := by
  push_neg    -- ❌ 无化简规则

修复:先 rw [not_iff] 手动拆解。

19.7.5. 失败 5:by_contra! 展开不完全🔗

by_contra! 内部调用 push_neg,若 push_neg 无法处理某层否定, 展开会在中途停止。检查 h 的类型是否符合预期。

19.8. 18.8 调试🔗

检查 push_neg 做了什么:在 push_neg 后暂停,观察 Infoview。 如果目标不变,原因有三:否定已在原子位置;缺少 @[push_neg] 引理; 表达式模式不匹配。

查看使用的引理

set_option trace.Meta.Tactic.simp true in
example : ¬(∀ x, P x) := by push_neg; sorry
-- trace 显示使用了 not_forall 等引理

contrapose 前后对比

example : P → Q := by
  trace_state; contrapose; trace_state; sorry

19.9. 18.9 与相关 tactic 的比较🔗

  • 简洁度 —— push_neg:高 —— simp only [not_forall, ...]:中 —— 手动 rw:低

  • 可控性 —— push_neg:低(全部展开) —— simp only [not_forall, ...]:高(选引理) —— 手动 rw:最高

  • 前提 —— contrapose:目标是蕴含 —— by_contra:任意

  • 构造性 —— contrapose:是 —— by_contra:否(排中律)

  • 产出 —— contrapose:新蕴含目标 —— by_contrah : ¬Goal ⊢ False

19.10. 18.10 练习🔗

19.10.1. 练习 1(基础 · push_neg🔗

example : ¬(∀ n : ℕ, n = 0) → ∃ n : ℕ, n ≠ 0 := by
  sorry

提示:push_neg 一步到位后 exact

19.10.2. 练习 2(基础 · contrapose)🔗

example (m n : ℕ) : m * n = 0 → m = 0 ∨ n = 0 := by
  contrapose
  sorry

提示:逆否后 push_neg,然后用 Nat.mul_ne_zero

19.10.3. 练习 3(判断 · 选择 tactic)🔗

以下三个目标分别最适合 push_negcontraposeby_contra 中的哪个?

-- (a) 直接证明
example (f : ℝ → ℝ) (hf : Monotone f) : ∀ a b, a ≤ b → f a ≤ f b := by sorry

-- (b) 否定展开
example : ¬(∃ n : ℕ, ∀ m, m < n) := by sorry

-- (c) 反证法
example (n : ℕ) (h : ∀ d, d ∣ n → d = 1 ∨ d = n) (hn : n ≥ 2) :
    Nat.Prime n := by sorry

19.10.4. 练习 4(进阶 · 组合)🔗

by_contra! 开头。

example (a : ℕ → ℝ) (h : ∀ n, a n < a (n + 1)) :
    ∀ m n, m < n → a m < a n := by
  sorry

19.10.5. 练习 5(进阶 · 分析学)🔗

证明:a ≤ b + ε 对所有正 ε 成立则 a ≤ b

example (a b : ℝ) (h : ∀ ε > 0, a ≤ b + ε) : a ≤ b := by
  sorry

19.10.6. 练习 6(挑战 · 稠密性)🔗

example (a b : ℝ) (hab : a < b) : ∃ q : ℚ, a < ↑q ∧ ↑q < b := by
  sorry

提示:by_contra! 不是唯一路径,考虑 Mathlib 的 exists_rat_btwn

19.11. 18.11 小结🔗

  • tactic:push_neg —— 作用:否定下推到原子公式 —— 内部机制:特化 simp + @[push_neg] 引理集 —— 变体:at hat *

  • tactic:contrapose —— 作用:蕴含 → 逆否 —— 内部机制:逻辑等价变换 —— 变体:contrapose!(+ push_neg)

  • tactic:by_contra —— 作用:反证法 —— 内部机制:排中律 Classical.em —— 变体:by_contra!(+ push_neg)

核心要点

  • push_neg 的能力取决于 @[push_neg] 引理集——自定义类型需注册

  • contrapose 要求目标是蕴含形式

  • by_contra 是非构造性的,尽量只在直接证明困难时使用

  • ! 的变体自动 push_neg,在含 /</量词时省去手动步骤