This module contains an implementation of a verified Tseitin transformation on AIGs. The key results
are the toCNF
function and the toCNF_equisat
correctness statement. The implementation is
done in the style of section 3.4 of the AIGNET paper.
Produce a Tseitin style CNF for a Decl.const
, using output
as the tree node variable.
Equations
- Std.Sat.AIG.Decl.constToCNF output const = [[(output, const)]]
Instances For
Produce a Tseitin style CNF for a Decl.atom
, using output
as the tree node variable.
Equations
Instances For
Produce a Tseitin style CNF for a Decl.gate
, using output
as the tree node variable.
Equations
Instances For
Instances For
Mix:
- An assignment for AIG atoms
- An assignment for auxiliary Tseitin variables into an assignment that can be used by a CNF produced by our Tseitin transformation.
Equations
- Std.Sat.AIG.toCNF.mixAssigns assign1 assign2 (Sum.inl var) = assign1 var
- Std.Sat.AIG.toCNF.mixAssigns assign1 assign2 (Sum.inr var) = assign2 var
Instances For
Project the atom assignment out of a CNF assignment
Equations
- Std.Sat.AIG.toCNF.projectLeftAssign assign x = assign (Sum.inl x)
Instances For
Project the auxiliary variable assignment out of a CNF assignment
Equations
- Std.Sat.AIG.toCNF.projectRightAssign assign idx h = assign (Sum.inr ⟨idx, h⟩)
Instances For
Given an atom assignment, produce an assignment that will always satisfy the CNF generated by our Tseitin transformation. This is done by combining the atom assignment with an assignment for the auxiliary variables, that just evaluates the AIG at the corresponding node.
Equations
- Std.Sat.AIG.toCNF.cnfSatAssignment aig assign1 = Std.Sat.AIG.toCNF.mixAssigns assign1 fun (idx : Fin aig.decls.size) => ⟦assign1, { aig := aig, ref := { gate := ↑idx, hgate := ⋯ } }⟧
Instances For
The central invariants for the Cache
.
- hmark : ∀ (lhs rhs : Nat) (linv rinv : Bool) (idx : Nat) (hbound : idx < aig.decls.size), marks[idx] = true → ∀ (heq : aig.decls[idx] = Std.Sat.AIG.Decl.gate lhs rhs linv rinv), marks[lhs] = true ∧ marks[rhs] = true
If there exists an AIG node that is marked, its children are also guaranteed to be marked already. This allows us to conclude that if a gate node is marked, all of its (transitive) children are also marked.
- heval : ∀ (assign : aig.CNFVar → Bool), Std.Sat.CNF.eval assign cnf = true → ∀ (idx : Nat) (hbound : idx < aig.decls.size), marks[idx] = true → ⟦Std.Sat.AIG.toCNF.projectLeftAssign assign, { aig := aig, ref := { gate := idx, hgate := hbound } }⟧ = Std.Sat.AIG.toCNF.projectRightAssign assign idx hbound
Relate satisfiability results about our produced CNF to satisfiability results about the AIG that we are processing. The intuition for this is: if a node is marked, its CNF (and all required children CNFs according to
hmark
) are already part of the current CNF. Thus the current CNF is already mirroring the semantics of the marked node. This means that if the CNF is satisfiable at some assignment, we can evaluate the marked node under the atom part of that assignment and will get the value that was assigned to the corresponding auxiliary variable as a result.
Instances For
If there exists an AIG node that is marked, its children are also guaranteed to be marked already. This allows us to conclude that if a gate node is marked, all of its (transitive) children are also marked.
Relate satisfiability results about our produced CNF to satisfiability results about the AIG that
we are processing. The intuition for this is: if a node is marked, its CNF (and all required
children CNFs according to hmark
) are already part of the current CNF. Thus the current CNF is
already mirroring the semantics of the marked node. This means that if the CNF is satisfiable at
some assignment, we can evaluate the marked node under the atom part of that assignment and will
get the value that was assigned to the corresponding auxiliary variable as a result.
The Cache
invariant always holds for an empty CNF when all nodes are unmarked.
The CNF cache. It keeps track of AIG nodes that we already turned into CNF to avoid adding the same CNF twice.
Keeps track of AIG nodes that we already turned into CNF.
- hmarks : self.marks.size = aig.decls.size
There are always as many marks as AIG nodes.
- inv : Std.Sat.AIG.toCNF.Cache.Inv cnf self.marks ⋯
Instances For
There are always as many marks as AIG nodes.
The invariant to make sure that marks
is well formed with respect to the cnf
We say that a cache extends another by an index when it doesn't invalidate any entry and has an entry for that index.
- extension : ∀ (idx : Nat) (hidx : idx < aig.decls.size), cache1.marks[idx] = true → cache2.marks[idx] = true
No entry is invalidated.
The second cache is true at the new index.
Instances For
No entry is invalidated.
The second cache is true at the new index.
Cache extension is a reflexive relation.
A cache with no entries is valid for an empty CNF.
Equations
- Std.Sat.AIG.toCNF.Cache.init aig = { marks := mkArray aig.decls.size false, hmarks := ⋯, inv := ⋯ }
Instances For
Add a Decl.const
to a Cache
.
Equations
Instances For
Add a Decl.atom
to a cache.
Equations
Instances For
Add a Decl.gate
to a cache.
Equations
Instances For
The key invariant about the State
itself (without cache): The CNF we produce is always satisfiable
at cnfSatAssignment
.
Equations
- Std.Sat.AIG.toCNF.State.Inv cnf = ∀ (assign1 : Nat → Bool), Std.Sat.CNF.Sat (Std.Sat.AIG.toCNF.cnfSatAssignment aig assign1) cnf
Instances For
The State
invariant always holds when we have an empty CNF.
Combining two CNFs for which State.Inv
holds preserves State.Inv
.
State.Inv
holds for the CNF that we produce for a Decl.const
.
State.Inv
holds for the CNF that we produce for a Decl.atom
State.Inv
holds for the CNF that we produce for a Decl.gate
The state to accumulate CNF clauses as we run our Tseitin transformation on the AIG.
- cnf : Std.Sat.CNF aig.CNFVar
The CNF clauses so far.
- cache : Std.Sat.AIG.toCNF.Cache aig self.cnf
A cache so that we don't generate CNF for an AIG node more than once.
- inv : Std.Sat.AIG.toCNF.State.Inv self.cnf
The invariant that
cnf
has to maintain as we build it up.
Instances For
The invariant that cnf
has to maintain as we build it up.
An initial state with no CNF clauses and an empty cache.
Equations
- Std.Sat.AIG.toCNF.State.empty aig = { cnf := [], cache := Std.Sat.AIG.toCNF.Cache.init aig, inv := ⋯ }
Instances For
State extension are Cache.IsExtensionBy
for now.
Equations
- state1.IsExtensionBy state2 new hnew = state1.cache.IsExtensionBy state2.cache new hnew
Instances For
State extension is a reflexive relation.
Add the CNF for a Decl.const
to the state.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Add the CNF for a Decl.atom
to the state.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Add the CNF for a Decl.gate
to the state.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Evaluate the CNF contained within the state.
Equations
- Std.Sat.AIG.toCNF.State.eval assign state = Std.Sat.CNF.eval assign state.cnf
Instances For
The CNF within the state is sat.
Equations
- Std.Sat.AIG.toCNF.State.Sat assign state = Std.Sat.CNF.Sat assign state.cnf
Instances For
The CNF within the state is unsat.
Equations
- state.Unsat = state.cnf.Unsat
Instances For
Convert an AIG into CNF, starting at some entry node.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- Std.Sat.AIG.toCNF.inj (Sum.inl var_1) = aig.decls.size + var_1
- Std.Sat.AIG.toCNF.inj (Sum.inr var_1) = ↑var_1
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
The function we use to convert from CNF with explicit auxiliary variables to just Nat
variables
in toCNF
is an injection.
The node that we started CNF conversion at will always be marked as visited in the CNF cache.
The CNF returned by go
will always be SAT at cnfSatAssignment
.
Connect SAT results about the CNF to SAT results about the AIG.
Connect SAT results about the AIG to SAT results about the CNF.
An AIG is unsat iff its CNF is unsat.