Lean 语言参考手册

16.5. 情形分析🔗

除同余闭包和约束传播外,grind 还会进行情形分析。 进行情形分析时,grind 会考虑一个项所有可能的构造方式,或某个特定项的每个可能取值,其方式类似于 casessplit 策略。 这种情形分析并非穷举式的:grind 只会在配置的深度上限内递归拆分情形,并由配置选项和标注控制哪些项可作为拆分候选。

16.5.1. 选择启发式方法🔗

grind 综合以下三类信号来决定要拆分哪个子项:

结构标志

以下配置标志决定 grind 是否进行特定的情形拆分:

splitIte(默认 true

拆分每个 Lean.Parser.Term.iteif 项,就像使用 split 策略一样。

splitMatch(默认 true

拆分每个 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 项,就像使用 split 策略一样。

splitImp(默认 false

对于形如 A B 且前件 A命题的假设,通过考虑 A 的所有可能情况进行拆分。 算术前件会受到特殊处理:如果 A 是算术文字(即由 =¬Dvd 等运算符构成的命题),那么即使 splitImp := falsegrind 也会拆分它,以便整数求解器传播事实。

全局限制

grindsplits := n 选项限制搜索树的深度。 一旦某个分支进行了 n 次拆分,grind 就不再继续拆分该分支;如果无法关闭该分支,它会报告已达到拆分阈值。

手动标注

可以用 grind cases 属性标记归纳谓词或结构。 grind 会将该谓词的每个实例视为拆分候选。

属性情形分析
attr ::= ...
    | Marks a theorem or definition for use by the `grind` tactic.

An optional modifier (e.g. `=`, `→`, `←`, `cases`, `intro`, `ext`, `inj`, etc.)
controls how `grind` uses the declaration:
* whether it is applied forwards, backwards, or both,
* whether equalities are used on the left, right, or both sides,
* whether case-splits, constructors, extensionality, or injectivity are applied,
* or whether custom instantiation patterns are used.

See the individual modifier docstrings for details.
grind The `cases` modifier marks inductively-defined predicates as suitable for case splitting.
cases

cases 修饰符将归纳定义的谓词标记为适合进行情形拆分。

属性及早情形分析
attr ::= ...
    | Marks a theorem or definition for use by the `grind` tactic.

An optional modifier (e.g. `=`, `→`, `←`, `cases`, `intro`, `ext`, `inj`, etc.)
controls how `grind` uses the declaration:
* whether it is applied forwards, backwards, or both,
* whether equalities are used on the left, right, or both sides,
* whether case-splits, constructors, extensionality, or injectivity are applied,
* or whether custom instantiation patterns are used.

See the individual modifier docstrings for details.
grind The `cases eager` modifier marks inductively-defined predicates as suitable for case splitting,
and instructs `grind` to perform it eagerly while preprocessing hypotheses.
cases eager

cases eager 修饰符将归纳定义的谓词标记为适合进行情形拆分,并指示 grind 在预处理假设时立即拆分。

拆分条件表达式

在此示例中,grind 通过考虑条件表达式的两种情况来证明定理:

example (c : Bool) (x y : Nat) (h : (if c then x else y) = 0) : x = 0 y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0x = 0 y = 0 All goals completed! 🐙

禁用 splitIte 会导致证明失败:

example (c : Bool) (x y : Nat) (h : (if c then x else y) = 0) : x = 0 y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0x = 0 y = 0 `grind` failed c:Boolx y:Nath:(if c = true then x else y) = 0left:¬x = 0right:¬y = 0False
[grind] Goal diagnostics
  • [facts] Asserted facts
    • [prop] (if c = true then x else y) = 0
    • [prop] ¬x = 0
    • [prop] ¬y = 0
  • [eqc] False propositions
    • [prop] x = 0
    • [prop] y = 0
  • [eqc] Equivalence classes
    • [eqc] others
      • [eqc] {0, if c = true then x else y}
  • [cutsat] Assignment satisfying linear constraints
    • [assign] x := 1
    • [assign] y := 2
All goals completed! 🐙

具体而言,在发现条件表达式等于 0 后,它无法继续推进:

`grind` failed
c:Boolx y:Nath:(if c = true then x else y) = 0left:¬x = 0right:¬y = 0False
[grind] Goal diagnostics
  • [facts] Asserted facts
    • [prop] (if c = true then x else y) = 0
    • [prop] ¬x = 0
    • [prop] ¬y = 0
  • [eqc] False propositions
    • [prop] x = 0
    • [prop] y = 0
  • [eqc] Equivalence classes
    • [eqc] others
      • [eqc] {0, if c = true then x else y}
  • [cutsat] Assignment satisfying linear constraints
    • [assign] x := 1
    • [assign] y := 2

禁止所有情形拆分会因同样的原因导致证明失败:

example (c : Bool) (x y : Nat) (h : (if c then x else y) = 0) : x = 0 y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0x = 0 y = 0 `grind` failed c:Boolx y:Nath:(if c = true then x else y) = 0left:¬x = 0right:¬y = 0False
[grind] Goal diagnostics
  • [facts] Asserted facts
    • [prop] (if c = true then x else y) = 0
    • [prop] ¬x = 0
    • [prop] ¬y = 0
  • [eqc] False propositions
    • [prop] x = 0
    • [prop] y = 0
  • [eqc] Equivalence classes
    • [eqc] others
      • [eqc] {0, if c = true then x else y}
  • [cutsat] Assignment satisfying linear constraints
    • [assign] x := 1
    • [assign] y := 2
  • [limits] Thresholds reached
    • [limit] maximum number of case-splits has been reached, threshold: `(splits := 0)`
All goals completed! 🐙
`grind` failed
c:Boolx y:Nath:(if c = true then x else y) = 0left:¬x = 0right:¬y = 0False
[grind] Goal diagnostics
  • [facts] Asserted facts
    • [prop] (if c = true then x else y) = 0
    • [prop] ¬x = 0
    • [prop] ¬y = 0
  • [eqc] False propositions
    • [prop] x = 0
    • [prop] y = 0
  • [eqc] Equivalence classes
    • [eqc] others
      • [eqc] {0, if c = true then x else y}
  • [cutsat] Assignment satisfying linear constraints
    • [assign] x := 1
    • [assign] y := 2
  • [limits] Thresholds reached
    • [limit] maximum number of case-splits has been reached, threshold: `(splits := 0)`

只允许一次拆分便已足够:

example (c : Bool) (x y : Nat) (h : (if c then x else y) = 0) : x = 0 y = 0 := c:Boolx:Naty:Nath:(if c = true then x else y) = 0x = 0 y = 0 All goals completed! 🐙
拆分模式匹配

在此示例中,禁用对模式匹配的情形拆分会导致 grind 失败:

example (h : y = match x with | 0 => 1 | _ => 2) : y > 0 := y:Natx:Nath:y = match x with | 0 => 1 | x => 2y > 0 `grind` failed y x:Nath:y = match x with | 0 => 1 | x => 2h_1:y = 0False
[grind] Goal diagnostics
  • [facts] Asserted facts
    • [prop] y = match x with | 0 => 1 | x => 2
    • [prop] y = 0
    • [prop] (x = 0 False) (match x with | 0 => 1 | x => 2) = 2
  • [eqc] True propositions
    • [prop] (x = 0 False) (match x with | 0 => 1 | x => 2) = 2
  • [eqc] Equivalence classes
    • [eqc] {y, 0}
      • [eqc] {match x with | 0 => 1 | x => 2}
    • [eqc] {x = 0 False, (fun x_0 => x_0 = 0 False) x, x = 0 False}
  • [ematch] E-matching patterns
    • [thm] _example.match_1.congr_eq_1: [_example.match_1 #4 (@Lean.Grind.genPattern `[Nat] #0 #3 `[0]) #2 #1]
    • [thm] _example.match_1.congr_eq_2: [_example.match_1 #6 (@Lean.Grind.genPattern `[Nat] #1 #5 #2) #4 #3]
  • [cutsat] Assignment satisfying linear constraints
    • [assign] y := 0
    • [assign] x := 1
    • [assign] match x with | 0 => 1 | x => 2 := 0
[grind] Diagnostics
  • [thm] E-Matching instances
    • [thm] _example.match_1.congr_eq_21
All goals completed! 🐙
`grind` failed
y x:Nath:y =
  match x with
  | 0 => 1
  | x => 2h_1:y = 0False
[grind] Goal diagnostics
  • [facts] Asserted facts
    • [prop] y = match x with | 0 => 1 | x => 2
    • [prop] y = 0
    • [prop] (x = 0 False) (match x with | 0 => 1 | x => 2) = 2
  • [eqc] True propositions
    • [prop] (x = 0 False) (match x with | 0 => 1 | x => 2) = 2
  • [eqc] Equivalence classes
    • [eqc] {y, 0}
      • [eqc] {match x with | 0 => 1 | x => 2}
    • [eqc] {x = 0 False, (fun x_0 => x_0 = 0 False) x, x = 0 False}
  • [ematch] E-matching patterns
    • [thm] _example.match_1.congr_eq_1: [_example.match_1 #4 (@Lean.Grind.genPattern `[Nat] #0 #3 `[0]) #2 #1]
    • [thm] _example.match_1.congr_eq_2: [_example.match_1 #6 (@Lean.Grind.genPattern `[Nat] #1 #5 #2) #4 #3]
  • [cutsat] Assignment satisfying linear constraints
    • [assign] y := 0
    • [assign] x := 1
    • [assign] match x with | 0 => 1 | x => 2 := 0
[grind] Diagnostics
  • [thm] E-Matching instances
    • [thm] _example.match_1.congr_eq_21

启用该选项后证明成功:

example (h : y = match x with | 0 => 1 | _ => 2) : y > 0 := y:Natx:Nath:y = match x with | 0 => 1 | x => 2y > 0 All goals completed! 🐙
拆分谓词

Not30 以一种略显冗长的方式表述一个数不等于 30

inductive Not30 : Nat Prop where | gt : x > 30 Not30 x | lt : x < 30 Not30 x

默认情况下,grind 无法证明 Not30 确实蕴含该数不等于 30

example : Not30 n n 30 := n:NatNot30 n n 30 `grind` failed n:Nath:Not30 nh_1:n = 30False
[grind] Goal diagnostics
  • [facts] Asserted facts
  • [eqc] True propositions
  • [eqc] Equivalence classes
    • [eqc] {n, 30}
  • [cutsat] Assignment satisfying linear constraints
    • [assign] n := 30
All goals completed! 🐙

这是因为 grind 没有考虑 Not30 的两种情形:

`grind` failed
n:Nath:Not30 nh_1:n = 30False
[grind] Goal diagnostics
  • [facts] Asserted facts
  • [eqc] True propositions
  • [eqc] Equivalence classes
    • [eqc] {n, 30}
  • [cutsat] Assignment satisfying linear constraints
    • [assign] n := 30

Not30 添加 grind cases 属性后,证明便能成功:

attribute [grind cases] Not30 example : Not30 n n 30 := n:NatNot30 n n 30 All goals completed! 🐙

类似地,Even 上的 grind cases 属性允许 grind 进行情形拆分:

@[grind cases] inductive Even : Nat Prop | zero : Even 0 | step : Even n Even (n + 2) attribute [grind cases] Even example (h : Even 5) : False := h:Even 5False All goals completed! 🐙 set_option trace.grind.split true in example (h : Even (n + 2)) : Even n := n:Nath:Even (n + 2)Even n [grind.split] Even (n + 2), generation: 0All goals completed! 🐙

16.5.2. 性能🔗

情形分析功能强大,但计算代价高昂:每增加一层情形拆分,搜索空间都会成倍增长。 因此务必谨慎,避免不必要的拆分。 具体而言:

  • 仅当目标确实需要更深的分支时才增大 splits;每多一层都会成倍扩大搜索空间。

  • 当大型模式匹配定义使搜索树急剧膨胀时,禁用 splitMatch;可设置 trace.grind.split 来观察这种情况。

  • 标志可以组合使用,例如 by grind -splitMatch (splits := 10) +splitImp

  • grind cases 属性是有作用域的。 修饰符 Lean.Parser.Term.attrKind`attrKind` 匹配 `("scoped" <|> "local")?`,用于属性之前,例如 `@[local simp]`。localLean.Parser.Term.attrKind`attrKind` 匹配 `("scoped" <|> "local")?`,用于属性之前,例如 `@[local simp]`。scoped 可将额外拆分限制在某个节或命名空间内。

🔗选项
trace.grind.split

默认值:false

启用后,grind 会输出关于命题拆分过程的跟踪消息。