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 等运算符形成的命题),则 grind 将在以下情况下拆分 even: splitImp := false 因此整数求解器可以传播事实。

全球限制

grind 选项 splits := n 限制搜索树的深度。 一旦分支执行 n 拆分,grind 就会停止在该分支中进一步拆分;如果无法关闭分支,则报告已达到拆分阈值。

手动注释

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

attributeCase Analysis
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

The cases modifier marks inductively-defined predicates as suitable for case splitting.

attributeEager Case Analysis
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

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

Splitting Conditional Expressions

在此示例中,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! 🐙
Splitting Pattern Matching

在此示例中,禁用模式匹配的大小写分割会导致 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! 🐙
Splitting Predicates

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

grind cases 属性添加到 Not30 可以使证明成功:

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 属性是 scoped。 修饰符 Lean.Parser.Term.attrKind`attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. localLean.Parser.Term.attrKind`attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. scoped 限制对节或命名空间的额外拆分。

🔗option
trace.grind.split

Default value: false

enable/disable tracing for the given module and submodules