feat: omega: more helpful error messages (#3847)
while trying to help a user who was facing an unhelpful ``` omega did not find a contradiction: [0, 0, 0, 0, 1, -1] ∈ [1, ∞) [0, 0, 0, 0, 0, 1] ∈ [0, ∞) [0, 0, 0, 0, 1] ∈ [0, ∞) [1, -1] ∈ [1, ∞) [0, 0, 0, 1] ∈ [0, ∞) [0, 1] ∈ [0, ∞) [1] ∈ [0, ∞) [0, 0, 0, 1, 1] ∈ [-1, ∞) ``` I couldn’t resist and wrote a pretty-printer for these problem that shows the linear combination as such, and includes the recognized atoms. This is especially useful since oftem `omega` failures stem from failure to recognize atoms as equal. In this case, we now get: ``` omega-failure.lean:19:2-19:7: error: omega could not prove the goal: a possible counterexample may satisfy the constraints d - e ≥ 1 e ≥ 0 d ≥ 0 a - b ≥ 1 c ≥ 0 b ≥ 0 a ≥ 0 c + d ≥ -1 where a := ↑(sizeOf xs) b := ↑(sizeOf x) c := ↑(sizeOf x.fst) d := ↑(sizeOf x.snd) e := ↑(sizeOf xs) ``` and this might help the user make progress (e.g. by using `case x` first, and investingating why `sizeOf xs` shows up twice)
This commit is contained in:
parent
535427ada4
commit
784972462a
4 changed files with 214 additions and 1 deletions
|
|
@ -175,6 +175,32 @@ def toSuperDigits (n : Nat) : List Char :=
|
|||
def toSuperscriptString (n : Nat) : String :=
|
||||
(toSuperDigits n).asString
|
||||
|
||||
def subDigitChar (n : Nat) : Char :=
|
||||
if n = 0 then '₀' else
|
||||
if n = 1 then '₁' else
|
||||
if n = 2 then '₂' else
|
||||
if n = 3 then '₃' else
|
||||
if n = 4 then '₄' else
|
||||
if n = 5 then '₅' else
|
||||
if n = 6 then '₆' else
|
||||
if n = 7 then '₇' else
|
||||
if n = 8 then '₈' else
|
||||
if n = 9 then '₉' else
|
||||
'*'
|
||||
|
||||
partial def toSubDigitsAux : Nat → List Char → List Char
|
||||
| n, ds =>
|
||||
let d := subDigitChar <| n % 10;
|
||||
let n' := n / 10;
|
||||
if n' = 0 then d::ds
|
||||
else toSubDigitsAux n' (d::ds)
|
||||
|
||||
def toSubDigits (n : Nat) : List Char :=
|
||||
toSubDigitsAux n []
|
||||
|
||||
def toSubscriptString (n : Nat) : String :=
|
||||
(toSubDigits n).asString
|
||||
|
||||
end Nat
|
||||
|
||||
instance : Repr Nat where
|
||||
|
|
|
|||
|
|
@ -526,6 +526,82 @@ def cases₂ (mvarId : MVarId) (p : Expr) (hName : Name := `h) :
|
|||
| throwError "'cases' tactic failed, unexpected new hypothesis"
|
||||
return ((s₁.mvarId, f₁), (s₂.mvarId, f₂))
|
||||
|
||||
/--
|
||||
Helpful error message when omega cannot find a solution
|
||||
-/
|
||||
def formatErrorMessage (p : Problem) : OmegaM MessageData := do
|
||||
if p.possible then
|
||||
if p.isEmpty then
|
||||
return m!"it is false"
|
||||
else
|
||||
let as ← atoms
|
||||
let mask ← mentioned p.constraints
|
||||
let names ← varNames mask
|
||||
return m!"a possible counterexample may satisfy the constraints\n" ++
|
||||
m!"{prettyConstraints names p.constraints}\nwhere\n{prettyAtoms names as mask}"
|
||||
else
|
||||
-- formatErrorMessage should not be used in this case
|
||||
return "it is trivially solvable"
|
||||
where
|
||||
varNameOf (i : Nat) : String :=
|
||||
let c : Char := .ofNat ('a'.toNat + (i % 26))
|
||||
let suffix := if i < 26 then "" else s!"_{i / 26}"
|
||||
s!"{c}{suffix}"
|
||||
|
||||
inScope (s : String) : MetaM Bool := do
|
||||
let n := .mkSimple s
|
||||
if (← resolveGlobalName n).isEmpty then
|
||||
if ((← getLCtx).findFromUserName? n).isNone then
|
||||
return false
|
||||
return true
|
||||
|
||||
-- Assign ascending names a, b, c, …, z, a1 … to all atoms mentioned according to the mask
|
||||
-- but avoid names in the local or global scope
|
||||
varNames (mask : Array Bool) : MetaM (Array String) := do
|
||||
let mut names := #[]
|
||||
let mut next := 0
|
||||
for h : i in [:mask.size] do
|
||||
if mask[i] then
|
||||
while ← inScope (varNameOf next) do next := next + 1
|
||||
names := names.push (varNameOf next)
|
||||
next := next + 1
|
||||
else
|
||||
names := names.push "(masked)"
|
||||
return names
|
||||
|
||||
prettyConstraints (names : Array String) (constraints : HashMap Coeffs Fact) : String :=
|
||||
constraints.toList
|
||||
|>.map (fun ⟨coeffs, ⟨_, cst, _⟩⟩ => " " ++ prettyConstraint (prettyCoeffs names coeffs) cst)
|
||||
|> "\n".intercalate
|
||||
|
||||
prettyConstraint (e : String) : Constraint → String
|
||||
| ⟨none, none⟩ => s!"{e} is unconstrained" -- should not happen in error messages
|
||||
| ⟨none, some y⟩ => s!"{e} ≤ {y}"
|
||||
| ⟨some x, none⟩ => s!"{e} ≥ {x}"
|
||||
| ⟨some x, some y⟩ =>
|
||||
if y < x then "∅" else -- should not happen in error messages
|
||||
s!"{x} ≤ {e} ≤ {y}"
|
||||
|
||||
prettyCoeffs (names : Array String) (coeffs : Coeffs) : String :=
|
||||
coeffs.toList.enum
|
||||
|>.filter (fun (_,c) => c ≠ 0)
|
||||
|>.enum
|
||||
|>.map (fun (j, (i,c)) =>
|
||||
(if j > 0 then if c > 0 then " + " else " - " else if c > 0 then "" else "- ") ++
|
||||
(if Int.natAbs c = 1 then names[i]! else s!"{c.natAbs}*{names[i]!}"))
|
||||
|> String.join
|
||||
|
||||
mentioned (constraints : HashMap Coeffs Fact) : OmegaM (Array Bool) := do
|
||||
let initMask := Array.mkArray (← getThe State).atoms.size false
|
||||
return constraints.fold (init := initMask) fun mask coeffs _ =>
|
||||
coeffs.enum.foldl (init := mask) fun mask (i, c) =>
|
||||
if c = 0 then mask else mask.set! i true
|
||||
|
||||
prettyAtoms (names : Array String) (atoms : Array Expr) (mask : Array Bool) : MessageData :=
|
||||
(Array.zip names atoms).toList.enum
|
||||
|>.filter (fun (i, _) => mask.getD i false)
|
||||
|>.map (fun (_, (n, a)) => m!" {n} := {a}")
|
||||
|> m!"\n".joinSep
|
||||
|
||||
mutual
|
||||
|
||||
|
|
@ -535,7 +611,7 @@ call `omegaImpl` in both branches.
|
|||
-/
|
||||
partial def splitDisjunction (m : MetaProblem) (g : MVarId) : OmegaM Unit := g.withContext do
|
||||
match m.disjunctions with
|
||||
| [] => throwError "omega did not find a contradiction:\n{m.problem}"
|
||||
| [] => throwError "omega could not prove the goal:\n{← formatErrorMessage m.problem}"
|
||||
| h :: t =>
|
||||
trace[omega] "Case splitting on {← inferType h}"
|
||||
let ctx ← getMCtx
|
||||
|
|
|
|||
32
tests/lean/omega-failure.lean
Normal file
32
tests/lean/omega-failure.lean
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/-!
|
||||
Testing omega's failure message
|
||||
-/
|
||||
|
||||
example : 0 < 0 := by omega
|
||||
|
||||
example (x : Nat) : x < 0 := by omega
|
||||
|
||||
example (x : Nat) (y : Int) : x < y := by omega
|
||||
|
||||
example (x y : Int) : 5 < x ∧ x < 10 → y > 0 := by omega
|
||||
|
||||
-- this used to fail with y = x, but then omega got better, so now there are unrelated x and y
|
||||
-- to make omega fail
|
||||
theorem sizeOf_snd_lt_sizeOf_list {α : Type u} {β : Type v} [SizeOf α] [SizeOf β]
|
||||
{x y : α × β} {xs : List (α × β)} :
|
||||
y ∈ xs → sizeOf x.snd < 1 + sizeOf xs
|
||||
:= by
|
||||
intro h
|
||||
have := List.sizeOf_lt_of_mem h
|
||||
have : sizeOf x = 1 + sizeOf x.1 + sizeOf x.2 := rfl
|
||||
omega
|
||||
|
||||
|
||||
example (reallyreallyreallyreally longlonglonglong namenamename : Nat) :
|
||||
reallyreallyreallyreally < longlonglonglong + namenamename := by omega
|
||||
|
||||
|
||||
def a := 1
|
||||
|
||||
example (b c d e f g h i j k l m n o p : Nat) :
|
||||
b + c + d + e + f + g + h + i + j + k + l + m + n + o + p < 100 := by omega
|
||||
79
tests/lean/omega-failure.lean.expected.out
Normal file
79
tests/lean/omega-failure.lean.expected.out
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
omega-failure.lean:5:22-5:27: error: omega could not prove the goal:
|
||||
it is false
|
||||
omega-failure.lean:7:32-7:37: error: omega could not prove the goal:
|
||||
a possible counterexample may satisfy the constraints
|
||||
a ≥ 0
|
||||
where
|
||||
a := ↑x
|
||||
omega-failure.lean:9:42-9:47: error: omega could not prove the goal:
|
||||
a possible counterexample may satisfy the constraints
|
||||
a - b ≥ 0
|
||||
a ≥ 0
|
||||
where
|
||||
a := ↑x
|
||||
b := y
|
||||
omega-failure.lean:11:51-11:56: error: omega could not prove the goal:
|
||||
a possible counterexample may satisfy the constraints
|
||||
b ≤ 0
|
||||
6 ≤ a ≤ 9
|
||||
where
|
||||
a := x
|
||||
b := y
|
||||
omega-failure.lean:22:2-22:7: error: omega could not prove the goal:
|
||||
a possible counterexample may satisfy the constraints
|
||||
d ≥ 0
|
||||
a - b ≥ 1
|
||||
c ≥ 0
|
||||
a - d ≤ -1
|
||||
b ≥ 0
|
||||
a ≥ 0
|
||||
c + d ≥ -1
|
||||
where
|
||||
a := ↑(sizeOf xs)
|
||||
b := ↑(sizeOf y)
|
||||
c := ↑(sizeOf x.fst)
|
||||
d := ↑(sizeOf x.snd)
|
||||
omega-failure.lean:26:67-26:72: error: omega could not prove the goal:
|
||||
a possible counterexample may satisfy the constraints
|
||||
c ≥ 0
|
||||
a - b - c ≥ 0
|
||||
b ≥ 0
|
||||
a ≥ 0
|
||||
where
|
||||
a := ↑reallyreallyreallyreally
|
||||
b := ↑longlonglonglong
|
||||
c := ↑namenamename
|
||||
omega-failure.lean:32:72-32:77: error: omega could not prove the goal:
|
||||
a possible counterexample may satisfy the constraints
|
||||
x ≥ 0
|
||||
a_1 ≥ 0
|
||||
v ≥ 0
|
||||
c_1 ≥ 0
|
||||
b_1 ≥ 0
|
||||
e_1 ≥ 0
|
||||
z ≥ 0
|
||||
q ≥ 0
|
||||
s ≥ 0
|
||||
d_1 ≥ 0
|
||||
u ≥ 0
|
||||
t ≥ 0
|
||||
w ≥ 0
|
||||
q + r + s + t + u + v + w + x + y + z + a_1 + b_1 + c_1 + d_1 + e_1 ≥ 100
|
||||
r ≥ 0
|
||||
y ≥ 0
|
||||
where
|
||||
q := ↑b
|
||||
r := ↑c
|
||||
s := ↑d
|
||||
t := ↑e
|
||||
u := ↑f
|
||||
v := ↑g
|
||||
w := ↑h
|
||||
x := ↑i
|
||||
y := ↑j
|
||||
z := ↑k
|
||||
a_1 := ↑l
|
||||
b_1 := ↑m
|
||||
c_1 := ↑n
|
||||
d_1 := ↑o
|
||||
e_1 := ↑p
|
||||
Loading…
Add table
Reference in a new issue