This repository contains my solution to an optional project from the course on Introduction to Computational Logic. The goal was to implement a model counting algorithm in Coq and verify its correctness.
A model for a formula
To generate the HTML documentation, run the following commands (tested with The Coq Proof Assistant, version 8.13.2):
./create_makefile.sh
make gallinahtml
Brute-force algorithm (link)
(* Generate all the possible assignments on a given set of variables. *)
Fixpoint all_assignments_on (vs : variables) : assignments :=
match vs with
| [] => [[]] | v::vs =>
map (cons (v,false)) (all_assignments_on vs) ++
map (cons (v,true)) (all_assignments_on vs)
end.
<...>
(* Given a function ϕ, the algorithm
1) Generates the list of all assignments on ϕ's variables
2) Keeps assignment such that ℇ ϕ α ≡ true.
3) Counts the number of assignments in the list. *)
Definition algorithm1 (ϕ : formula) : { n : nat | #sat ϕ ≃ n }.
Proof. ... Defined.
Section Tests.
(* We define some variables and formulas for testing. *)
Let x1 := [|V 1|].
Let x2 := [|V 2|].
Let x3 := [|V 3|].
Let x4 := [|V 4|].
Let x5 := [|V 5|].
(* Compute (proj1_sig (algorithm1 x1)). => 1 : nat *)
(* Compute (proj1_sig (algorithm1 (x1 ∨ x2 ∨ x3 ∨ x4 ∨ x5))). => 31 : nat *)
(* Compute (proj1_sig (algorithm1 (x1 ⊕ x2 ⊕ x3 ⊕ x4 ⊕ x5))). => 16 : nat *)
(* or_n n = x1 ∨ x2 ∨ ... ∨ xn *)
Let or_n n := fold_left (fun ϕ x => ϕ ∨ [|V x|]) (range 1 n) F.
(* Compute (proj1_sig (algorithm1 (or_n 8))). => 255 : nat *)
End Tests.
"Divide-and-conquer" algorithm (link)
(* The main idea of the algorithm is the following:
#sat F = 0
#sat T = 1
#sat ϕ = #sat (x ∧ ϕ[x ↦ T] ∨ ¬x ∧ ϕ[x ↦ F])
= #sat (x ∧ ϕ[x ↦ T]) + #sat (¬x ∧ ϕ[x ↦ F])
= #sat (ϕ[x ↦ T]) + #sat (ϕ[x ↦ F]). *)
Definition algorithm2 (ϕ : formula) : { n : nat | #sat ϕ ≃ n }.
Proof. ... Defined.
Section Tests.
(* or_n n = x1 ∨ x2 ∨ ... ∨ xn *)
Let or_n n := fold_left (fun ϕ x => ϕ ∨ [|V x|]) (range 1 n) F.
(* xor_n n = x1 ⊕ x2 ⊕ ... ⊕ xn *)
Let xor_n n := fold_left (fun ϕ x => ϕ ⊕ [|V x|]) (range 1 n) F.
(* Compute (proj1_sig (algorithm1 (or_n 5))). => 31 : nat *)
(* Compute (proj1_sig (algorithm1 (or_n 8))). => 255 : nat *)
(* Compute (proj1_sig (algorithm1 (or_n 10))). => 1023 : nat *)
(* Compute (proj1_sig (algorithm1 (or_n 15))). => 32767 : nat *)
(* Compute (proj1_sig (algorithm1 (or_n 16))). => Stack overflow. *)
(* Compute (proj1_sig (algorithm1 (xor_n 5))). => 16 : nat *)
(* Compute (proj1_sig (algorithm1 (xor_n 8))). => 128 : nat *)
End Tests.
Counting $k$ -cliques in a graph (link)
(* I'll use the problem of counting the k-cliques as a "generator" of
nontrivial boolean formulas. *)
(* A graph is a record with a list of vertices and a function
that returns true if there is an edge between two vertices. *)
Record graph :=
{ vtcs : list nat;
edges : nat -> nat -> bool;
}.
<...>
(* A graph with 4 vertices and 6 edges. *)
Definition graph_4_clique :=
{| vtcs := [1;2;3;4];
edges v1 v2 :=
match v1, v2 with
| 1,2 | 2,1 => true
| 1,3 | 3,1 => true
| 1,4 | 4,1 => true
| 2,3 | 3,2 => true
| 2,4 | 4,2 => true
| 3,4 | 4,3 => true
| _, _ => false
end;
|}.
<...>
(* Given a natural number [k] and a graph [g], generate a formula
that is true if and only if [g] contains a [k]-clique . *)
Definition transform (k : nat) (g : graph) : formula := <...>
(* Take ~10 sec. *)
(* Compute (counting_k_cliques 1 graph_4_clique). => 4 : nat *)
(* Compute (counting_k_cliques 2 graph_4_clique). => 6 : nat *)
(* Compute (counting_k_cliques 3 graph_4_clique). => 4 : nat *)
(* Takes ~240 sec. *)
(* Compute (counting_k_cliques 4 graph_4_clique). => 1 : nat *)