Skip to content

Commit 99ef243

Browse files
committed
Support unsat-core and counter-examples.
1 parent ef68801 commit 99ef243

File tree

20 files changed

+356
-122
lines changed

20 files changed

+356
-122
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ jobs:
3030
- name: Build and Test Lean-SMT
3131
run: |
3232
lake update
33-
lake build Smt Smt.Auto Smt.Rat Smt.Real
33+
lake build Smt Smt.Rat Smt.Real
3434
lake run test
3535
shell: bash

Smt/Options.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ open Lean
1414
initialize
1515
registerTraceClass `smt
1616
registerTraceClass `smt.attr
17+
registerTraceClass `smt.preprocess
1718
registerTraceClass `smt.translate
1819
registerTraceClass `smt.translate.expr
1920
registerTraceClass `smt.translate.query

Smt/Preprocess.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ Authors: Abdalrhman Mohamed, Tomaz Gomes Mascarenhas
66
-/
77

88
import Smt.Preprocess.Iff
9+
import Smt.Preprocess.Mono
10+
import Smt.Preprocess.PushHintsToCtx

Smt/Preprocess/Basic.lean

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/-
2+
Copyright (c) 2021-2024 by the authors listed in the file AUTHORS and their
3+
institutional affiliations. All rights reserved.
4+
Released under Apache 2.0 license as described in the file LICENSE.
5+
Authors: Abdalrhman Mohamed
6+
-/
7+
8+
import Lean.Meta.Basic
9+
import Lean.Meta.InferType
10+
11+
namespace Smt.Preprocess
12+
13+
open Lean
14+
15+
structure Result where
16+
map : Std.HashMap Expr (Array Expr)
17+
hs : Array Expr
18+
mv : MVarId
19+
20+
/-- Return all propositions in the local context. -/
21+
def getPropHyps : MetaM (Array FVarId) := do
22+
let mut result := #[]
23+
for localDecl in (← getLCtx) do
24+
unless localDecl.isImplementationDetail do
25+
if ← pure !(isNonEmpty localDecl.type) <&&> Meta.isProp localDecl.type then
26+
result := result.push localDecl.fvarId
27+
return result
28+
where
29+
isNonEmpty (e : Expr) : Bool :=
30+
match_expr e with
31+
| Nonempty _ => true
32+
| _ => false
33+
34+
end Smt.Preprocess

Smt/Preprocess/Iff.lean

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
55
Authors: Abdalrhman Mohamed, Tomaz Gomes Mascarenhas
66
-/
77

8-
import Lean
8+
import Smt.Preprocess.Basic
99
import Qq
1010

1111
namespace Smt.Preprocess
@@ -27,43 +27,46 @@ def replaceIff (e : Expr) : MetaM Expr :=
2727
def containsIff (e : Expr) : Bool :=
2828
(Expr.const ``Iff []).occurs e
2929

30-
def elimIff (mv : MVarId) (hs : List Expr) : MetaM (List Expr × MVarId) := mv.withContext do
30+
def elimIff (mv : MVarId) (hs : Array Expr) : MetaM Result := mv.withContext do
3131
let t ← instantiateMVars (← mv.getType)
3232
let ts ← hs.mapM (Meta.inferType · >>= instantiateMVars)
3333
if !(containsIff t || ts.any containsIff) then
34-
return (hs, mv)
34+
return { map := Std.HashMap.insertMany ∅ (hs.zip (hs.map .singleton)), hs, mv }
3535
let simpTheorems ← #[``eq_self, ``iff_eq_eq].foldlM (·.addConst ·) ({} : Meta.SimpTheorems)
3636
let simpTheorems := #[simpTheorems]
3737
let congrTheorems ← Meta.getSimpCongrTheorems
3838
let ctx ← Meta.Simp.mkContext {} simpTheorems congrTheorems
39-
let (hs, mv) ← elimIffLocalDecls mv hs ctx
40-
let mv ← elimIffTarget mv ctx
41-
return (hs, mv)
39+
let (hs', mv') ← elimIffLocalDecls mv hs.toList ctx #[]
40+
let mv' ← elimIffTarget mv' ctx
41+
return { map := Std.HashMap.insertMany ∅ (hs'.zip (hs.map .singleton)), hs := hs', mv := mv' }
4242
where
43-
elimIffLocalDecls mv hs ctx := mv.withContext do
44-
let mut newHs := []
45-
let mut toAssert := #[]
46-
for h in hs do
43+
elimIffLocalDecls mv hs ctx hs' := do match hs with
44+
| [] => return (hs', mv)
45+
| h :: hs =>
4746
let type ← Meta.inferType h
4847
let eq ← replaceIff (← instantiateMVars type)
4948
let (_, l, r) := eq.eq?.get!
5049
if l == r then
51-
newHs := h :: newHs
50+
elimIffLocalDecls mv hs ctx (hs'.push h)
5251
else
53-
let userName ← if h.isFVar then h.fvarId!.getUserName else Lean.mkFreshId
54-
let type := r
55-
let (r, _) ← Meta.simp eq ctx
56-
let value ← Meta.mkAppM ``eq_resolve #[h, ← Meta.mkOfEqTrue (← r.getProof)]
57-
toAssert := toAssert.push { userName, type, value }
58-
let (fvs, mv) ← mv.assertHypotheses toAssert
59-
newHs := newHs.reverse ++ (fvs.map (.fvar ·)).toList
60-
return (newHs, mv)
52+
let (res, _) ← Meta.simp eq ctx
53+
let h' := mkApp4 (.const ``eq_resolve []) l r h (mkOfEqTrue eq (← res.getProof))
54+
if let .some fv := h.fvarId? then
55+
let res ← mv.replace fv h' (.some r)
56+
let hs' := hs'.map res.subst.apply
57+
let hs := hs.map res.subst.apply
58+
res.mvarId.withContext (elimIffLocalDecls res.mvarId hs ctx (hs'.push (.fvar res.fvarId)))
59+
else
60+
elimIffLocalDecls mv hs ctx (hs'.push h')
61+
termination_by hs.length
6162
elimIffTarget mv ctx := mv.withContext do
6263
let eq ← replaceIff (← instantiateMVars (← mv.getType))
63-
let (r, _) ← Meta.simp eq ctx
64-
if r.expr.isTrue then
65-
mv.replaceTargetEq eq.appArg! (← Meta.mkOfEqTrue (← r.getProof))
64+
let (res, _) ← Meta.simp eq ctx
65+
if res.expr.isTrue then
66+
mv.replaceTargetEq eq.appArg! (mkOfEqTrue eq (← res.getProof))
6667
else
6768
return mv
69+
mkOfEqTrue p hpt :=
70+
mkApp2 (.const ``of_eq_true []) p hpt
6871

6972
end Smt.Preprocess

Smt/Auto.lean renamed to Smt/Preprocess/Mono.lean

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@ Authors: Abdalrhman Mohamed
66
-/
77

88
import Auto.Tactic
9+
import Smt.Preprocess.Basic
910

1011
namespace Auto
1112

1213
open Lean Elab Embedding.Lam
1314

15+
def DTr.contains (self : DTr) (other : DTr) : Bool :=
16+
if self == other then true
17+
else match self with
18+
| .leaf _ => false
19+
| .node _ dtrs => dtrs.attach.any fun ⟨dtr, _⟩ => dtr.contains other
20+
1421
structure InputHints' where
1522
lemmas : Array Lemma := #[]
1623
lemdbs : Array Name := #[]
@@ -147,7 +154,7 @@ def mono' (declName? : Option Name) (mv : MVarId) (hints : InputHints') (unfoldI
147154
let f (acc : MessageData) (dtr : FVarId × DTr) :=
148155
acc ++ m!"\n<{Expr.fvar dtr.fst}> = <{ToString.toString dtr.snd}>"
149156
let message : MessageData := dtrs.foldl f m!""
150-
trace[smt] "dtrs: {message}"
157+
trace[smt.preprocess] "dtrs: {message}"
151158
let (_, mv) ← mvarId.assertHypotheses hs
152159
absurd.assign proof
153160
return (mv, dtrs)
@@ -182,3 +189,40 @@ open Lean Elab Tactic in
182189
| _ => throwUnsupportedSyntax
183190

184191
end Auto.Tactic
192+
193+
namespace Smt.Preprocess
194+
195+
open Lean
196+
197+
open Auto in
198+
def hintsToAutoHints (hs : Array Expr) : MetaM (Std.HashMap DTr Expr × InputHints' × Array Prep.ConstUnfoldInfo × Array Name) := do
199+
let lemmas ← hs.mapM inferLemma
200+
let map := .ofList (lemmas.map (fun l => (l.deriv, l.proof))).toList
201+
return (map, ⟨lemmas, #[], false⟩, #[], #[])
202+
where
203+
inferLemma (e : Expr) : MetaM Lemma := do
204+
let paramNames ← getLevelParamNames e
205+
return Lemma.mk ⟨e, ← Meta.inferType e, (.node "smtHint" #[.leaf s!"{e}"])⟩ paramNames
206+
getLevelParamNames {ω : Type} {m} [STWorld ω m] [MonadLiftT (ST ω) m] [Monad m] (e : Expr) : m (Array Name) := do
207+
let ((), names) ← (e.forEach getLevelParamName).run (m := m) {}
208+
return names.toArray
209+
getLevelParamName {m} [Monad m] (e : Expr) : StateT (Std.HashSet Name) m Unit := do
210+
match e with
211+
| .const _ lvls =>
212+
let names := lvls.filterMap fun lvl => do
213+
let .param name := lvl | none
214+
some name
215+
modify fun s => s.insertMany names
216+
| _ => return
217+
218+
def mono (mv : MVarId) (hs : Array Expr) : MetaM Result := do
219+
let (invMap, hints, unfoldInfos, defeqNames) ← hintsToAutoHints hs
220+
let (mv, dtrs) ← Auto.mono' `smt mv hints unfoldInfos defeqNames
221+
let map := dtrs.foldl (init := {}) fun map (fv, dtr) =>
222+
let usedHints := invMap.filter (fun k _ => dtr.contains k)
223+
map.insert (.fvar fv) usedHints.valuesArray
224+
let hs ← mv.withContext (return (← getPropHyps).map Expr.fvar)
225+
trace[smt.preprocess] "goal: {mv}"
226+
return { map, hs, mv }
227+
228+
end Smt.Preprocess

Smt/Preprocess/PushHintsToCtx.lean

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/-
2+
Copyright (c) 2021-2024 by the authors listed in the file AUTHORS and their
3+
institutional affiliations. All rights reserved.
4+
Released under Apache 2.0 license as described in the file LICENSE.
5+
Authors: Abdalrhman Mohamed
6+
-/
7+
8+
import Smt.Preprocess.Basic
9+
import Lean.Meta.Tactic.Assert
10+
11+
namespace Smt.Preprocess
12+
13+
open Lean
14+
15+
def pushHintsToCtx (mv : MVarId) (hs : Array Expr) : MetaM Result := do
16+
hs.foldrM pushHint { map := {}, hs := #[], mv }
17+
where
18+
pushHint (h : Expr) (r : Result) : MetaM Result := do
19+
if h.isFVar || h.isConst then
20+
return { r with map := r.map.insert h #[h], hs := r.hs.push h }
21+
else
22+
let mv' ← r.mv.assert (← mkFreshId) (← Meta.inferType h) h
23+
let ⟨fv, mv'⟩ ← mv'.intro1
24+
let h' := .fvar fv
25+
return { map := r.map.insert h' #[h], hs := r.hs.push h', mv := mv' }
26+
27+
end Smt.Preprocess

Smt/Reconstruct.lean

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -215,35 +215,51 @@ partial def reconstructProof (pf : cvc5.Proof) (ctx : Reconstruct.Context) :
215215
else
216216
return (dfns, ps, p, h, mvs.toList)
217217

218+
inductive cvc5Result where
219+
| sat (model : Array (cvc5.Term × cvc5.Term))
220+
| unsat (pf : cvc5.Proof) (uc : Array cvc5.Term)
221+
| unknown (reason : cvc5.UnknownExplanation)
222+
218223
open cvc5 in
219-
def traceSolve (r : Except Exception (Except SolverError Proof)) : MetaM MessageData :=
224+
def traceSolve (r : Except Exception (Except Error cvc5Result)) : MetaM MessageData :=
220225
return match r with
221226
| .ok (.ok _) => m!"{checkEmoji}"
222227
| _ => m!"{bombEmoji}"
223228

224229
open cvc5 in
225-
def solve (query : String) (timeout : Option Nat) : MetaM (Except Error cvc5.Proof) :=
226-
profileitM Exception "simp" {} do
230+
def solve (query : String) (timeout : Option Nat) : MetaM (Except cvc5.Error cvc5Result) :=
231+
profileitM Exception "smt" {} do
227232
withTraceNode `smt.solve traceSolve do Solver.run (← TermManager.new) do
228233
if let .some timeout := timeout then
229234
Solver.setOption "tlimit" (toString (1000*timeout))
230235
Solver.setOption "dag-thresh" "0"
231236
Solver.setOption "simplification" "none"
232237
Solver.setOption "enum-inst" "true"
238+
Solver.setOption "enum-inst-interleave" "true"
233239
Solver.setOption "cegqi-midpoint" "true"
234240
Solver.setOption "produce-models" "true"
235241
Solver.setOption "produce-proofs" "true"
236242
Solver.setOption "proof-elim-subtypes" "true"
237243
Solver.setOption "proof-granularity" "dsl-rewrite"
238-
Solver.parseCommands query
239-
let r ← Solver.checkSat
240-
trace[smt.solve] m!"result: {r}"
241-
if r.isUnsat then
244+
let vs ← Solver.parseCommands query
245+
let res ← Solver.checkSat
246+
trace[smt.solve] m!"result: {res}"
247+
if res.isSat then
248+
let ts ← Solver.getValues vs
249+
trace[smt.solve] "unsat-core:\n{vs.zip ts}"
250+
return .sat (vs.zip ts)
251+
else if res.isUnsat then
242252
let ps ← Solver.getProof
253+
let uc ← Solver.getUnsatCore
243254
if h : 0 < ps.size then
244255
trace[smt.solve] "proof:\n{← Solver.proofToString ps[0]}"
245-
return ps[0]
246-
throw (self := instMonadExceptOfMonadExceptOf _ _) (Error.error s!"Expected unsat, got {r}")
256+
trace[smt.solve] "unsat-core:\n{uc}"
257+
return .unsat ps[0] uc
258+
else if res.isUnknown then
259+
let reason := res.getUnknownExplanation
260+
trace[smt.solve] "unknown-reason:\n{reason}"
261+
return .unknown reason
262+
throwError s!"unexpected check-sat result {res}"
247263

248264
syntax (name := reconstruct) "reconstruct" str : tactic
249265

@@ -253,8 +269,10 @@ open Lean.Elab Tactic in
253269
let some query := stx[1].isStrLit? | throwError "expected string"
254270
let r ← solve query none
255271
match r with
256-
| .error e => logInfo (repr e)
257-
| .ok pf =>
272+
| .error e => throwError e.toString
273+
| .ok (.sat _) => throwError "expected unsat result"
274+
| .ok (.unknown r) => logInfo (repr r)
275+
| .ok (.unsat pf _) =>
258276
let (_, _, p, hp, mvs) ← reconstructProof pf ⟨(← getUserNames), false⟩
259277
let mv ← Tactic.getMainGoal
260278
let mv ← mv.assert (Name.num `s 0) p hp

Smt/Reconstruct/Builtin.lean

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ where
6363
| .ITE =>
6464
let (u, (α : Q(Sort u))) ← reconstructSortLevelAndSort t.getSort
6565
let c : Q(Prop) ← reconstructTerm t[0]!
66-
let h : Q(Decidable $c) ← Meta.synthInstance q(Decidable $c)
66+
let oh : Option Q(Decidable $c) ← Meta.synthInstance? q(Decidable $c)
67+
let h : Q(Decidable $c) := oh.getD q(Classical.propDecidable $c)
6768
let x : Q($α) ← reconstructTerm t[1]!
6869
let y : Q($α) ← reconstructTerm t[2]!
6970
return q(@ite $α $c $h $x $y)

0 commit comments

Comments
 (0)