Skip to content

Commit a0ff968

Browse files
committed
feat: casting CNF state
1 parent db09477 commit a0ff968

1 file changed

Lines changed: 73 additions & 14 deletions

File tree

src/Std/Sat/AIG/CNF.lean

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,40 @@ def Cache.init (aig : AIG α) : Cache aig .empty where
237237
hmarks := by simp
238238
inv := Inv_init
239239

240+
/--
241+
Reuse a `Cache` for an `AIG` that extends the original one. This is useful for incrementally
242+
generating CNF formulas for AIGs.
243+
-/
244+
-- Nospecialize as the type classes are not used at runtime so any specialization here is pointless
245+
@[nospecialize]
246+
def Cache.cast {aig1 aig2 : AIG α} (cache : Cache aig1 cnf)
247+
(hprefix : IsPrefix aig1.decls aig2.decls) : Cache aig2 cnf :=
248+
-- Crucial: never refer to `aig1` here so it can be erased at runtime.
249+
have hsize := hprefix.size_le
250+
have hmarks := cache.hmarks
251+
{
252+
marks := cache.marks ++ Array.replicate (aig2.decls.size - cache.marks.size) false
253+
hmarks := by simp; omega
254+
inv := by
255+
intro assign heval idx hbound hmarked
256+
have hidx : idx < aig1.decls.size := by
257+
apply Classical.byContradiction
258+
intro hnot
259+
have : cache.marks.size ≤ idx := by omega
260+
simp [Array.getElem_append_right this] at hmarked
261+
rw [Array.getElem_append_left (by omega)] at hmarked
262+
have h1 := cache.inv assign heval idx hidx hmarked
263+
rw [denote.eq_of_isPrefix ⟨aig1, ⟨idx, false, hidx⟩⟩ aig2 hprefix, ← h1]
264+
apply denote_congr
265+
intro a hmem
266+
rw [mem_def] at hmem
267+
rcases Array.getElem_of_mem hmem with ⟨i, hi, hia⟩
268+
have h2 : aig2.decls[i]'(by omega) = .atom a := by
269+
rw [hprefix.idx_eq i hi]
270+
exact hia
271+
rw [projectLeftAssign_atom h2, projectLeftAssign_atom hia]
272+
}
273+
240274
/--
241275
Add a `Decl.false` to a `Cache`.
242276
-/
@@ -371,11 +405,17 @@ def Cache.addIte (cache : Cache aig cnf) {cond ifTrue ifFalse : Fanin} (idx : Na
371405
⟨out, IsExtensionBy_set cache out idx hmarkbound (by simp [out])⟩
372406

373407
/--
374-
The key invariant about the `State` itself (without cache): The CNF we produce is always satisfiable
375-
at `cnfSatAssignment`.
408+
The key invariant about the `State` itself (without cache):
409+
The CNF we produce is always satisfied by any assignment that evaluates the AIG at the variable
410+
of each node, in particular by `cnfSatAssignment`.
411+
412+
Note that this definition leaves variables that do not occur in the AIG unconstrained so that the
413+
CNF can be reused for an AIG that extends the current one.
376414
-/
377415
def State.Inv (aig : AIG α) (cnf : CNF Nat) : Prop :=
378-
∀ (assign1 : α → Bool), cnf.Sat (cnfSatAssignment aig assign1)
416+
∀ (assign1 : α → Bool) (assign : Nat → Bool),
417+
(∀ (idx : Nat) (h : idx < aig.decls.size), assign idx = ⟦aig, ⟨idx, false, h⟩, assign1⟧) →
418+
cnf.Sat assign
379419

380420
/--
381421
The `State` invariant always holds when we have an empty CNF.
@@ -388,9 +428,9 @@ Combining two CNFs for which `State.Inv` holds preserves `State.Inv`.
388428
-/
389429
theorem State.Inv_append (h1 : State.Inv aig cnf1) (h2 : State.Inv aig cnf2) :
390430
State.Inv aig (cnf1 ++ cnf2) := by
391-
intro assign1
392-
specialize h1 assign1
393-
specialize h2 assign1
431+
intro assign1 assign hagree
432+
specialize h1 assign1 assign hagree
433+
specialize h2 assign1 assign hagree
394434
simp [CNF.sat_def] at h1 h2 ⊢
395435
constructor <;> assumption
396436

@@ -400,22 +440,24 @@ theorem State.Inv_append (h1 : State.Inv aig cnf1) (h2 : State.Inv aig cnf2) :
400440
theorem State.Inv_falseToCNF {upper : Nat} {h : upper < aig.decls.size}
401441
(heq : aig.decls[upper] = .false) :
402442
State.Inv aig (Decl.falseToCNF upper) := by
403-
intro assign1
404-
simp [CNF.sat_def, denote_idx_false heq, h]
443+
intro assign1 assign hagree
444+
simp [CNF.sat_def, hagree upper h, denote_idx_false heq]
405445

406446
/--
407447
`State.Inv` holds for the CNF that we produce for a `Decl.gate`
408448
-/
409449
theorem State.Inv_gateToCNF {aig : AIG α} {h}
410450
(heq : aig.decls[upper]'h = .gate lhs rhs) :
411451
State.Inv aig (Decl.gateToCNF upper lhs.gate rhs.gate lhs.invert rhs.invert) := by
412-
intro assign1
452+
intro assign1 assign hagree
413453
have hlhs : lhs.gate < aig.decls.size := Nat.lt_trans (aig.hdag h heq).left h
414454
have hrhs : rhs.gate < aig.decls.size := Nat.lt_trans (aig.hdag h heq).right h
415455
generalize hlinv : lhs.invert = linv
416456
generalize hrinv : rhs.invert = rinv
417457
rw [CNF.sat_def]
418-
cases linv <;> cases rinv <;> simp [denote_idx_gate heq, hlinv, hrinv, h, hlhs, hrhs]
458+
cases linv <;> cases rinv
459+
<;> simp [denote_idx_gate heq, hlinv, hrinv, hagree upper h, hagree lhs.gate hlhs,
460+
hagree rhs.gate hrhs]
419461

420462
/--
421463
`State.Inv` holds for the CNF that we produce for an ITE.
@@ -429,9 +471,9 @@ theorem State.Inv_iteToCNF {aig : AIG α} {cond ifTrue ifFalse : Fanin} {idx : N
429471
⟦aig, ⟨ifTrue.gate, ifTrue.invert, by omega⟩, assign⟧
430472
⟦aig, ⟨ifFalse.gate, ifFalse.invert, by omega⟩, assign⟧) :
431473
State.Inv aig (Decl.iteToCNF idx cond.gate ifTrue.gate ifFalse.gate cond.invert ifTrue.invert ifFalse.invert) := by
432-
intro assign1
433-
rw [CNF.sat_def, Decl.iteToCNF_eval, satAssignment_lt h, hdenote, satAssignment_lt (by omega),
434-
satAssignment_lt (by omega), satAssignment_lt (by omega)]
474+
intro assign1 assign hagree
475+
rw [CNF.sat_def, Decl.iteToCNF_eval, hagree idx h, hdenote, hagree cond.gate (by omega),
476+
hagree ifTrue.gate (by omega), hagree ifFalse.gate (by omega)]
435477
have {fi : Fanin} {aig : AIG α} {h} {assign : α → Bool} :
436478
⟦aig, ⟨fi.gate, fi.invert, h⟩, assign⟧ = (⟦aig, ⟨fi.gate, false, h⟩, assign⟧ ^^ fi.invert) := by
437479
cases fi.invert <;> simp
@@ -462,6 +504,22 @@ def State.empty (aig : AIG α) : State aig where
462504
cache := Cache.init aig
463505
inv := State.Inv_nil
464506

507+
/--
508+
Reuse a `State` for an `AIG` that extends the original one, see `Cache.cast`.
509+
-/
510+
-- Nospecialize as the type classes are not used at runtime so any specialization here is pointless
511+
@[nospecialize]
512+
def State.cast {aig1 aig2 : AIG α} (state : State aig1) (hprefix : IsPrefix aig1.decls aig2.decls) :
513+
State aig2 where
514+
cnf := state.cnf
515+
cache := state.cache.cast hprefix
516+
inv := by
517+
intro assign1 assign hagree
518+
apply state.inv assign1 assign
519+
intro idx h
520+
rw [hagree idx (by have := hprefix.size_le; omega)]
521+
exact denote.eq_of_isPrefix ⟨aig1, ⟨idx, false, h⟩⟩ aig2 hprefix
522+
465523
/--
466524
State extension are `Cache.IsExtensionBy` for now.
467525
-/
@@ -795,7 +853,8 @@ The CNF returned by `go` will always be SAT at `cnfSatAssignment`.
795853
theorem toCNF.go_sat (aig : AIG α) (start : Nat) (h1 : start < aig.decls.size) (assign1 : α → Bool)
796854
(state : toCNF.State aig) :
797855
(go aig start h1 state).val.Sat (cnfSatAssignment aig assign1) := by
798-
have := (go aig start h1 state).val.inv assign1
856+
have := (go aig start h1 state).val.inv assign1 (cnfSatAssignment aig assign1)
857+
(fun _ h => satAssignment_lt h)
799858
rw [State.sat_iff]
800859
simp [this]
801860

0 commit comments

Comments
 (0)