-
Notifications
You must be signed in to change notification settings - Fork 1
/
smt.ml
524 lines (462 loc) · 13.9 KB
/
smt.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
open Printf;;
open Scanf;;
open Ast;;
open Equality_theory_solver;;
(* Type definition for SAT solver *)
type litteral = {id : int; negation : bool};;
type clause = litteral list;;
type cnf = clause list;;
type model_var = {var : litteral; inferred : bool};;
type model = model_var list;;
exception Unit_clause_found of litteral * clause;;
exception Undefined_litteral_found of litteral;;
exception Undefined_behaviour;;
(**************************)
(* Quick research of antecedent *)
module AnteMap =
Map.Make(struct
type t = int
let compare = compare
end)
;;
(* Quick research of level *)
module LevelMap =
Map.Make(struct
type t = int
let compare = compare
end)
;;
(**************************)
(* Printing functions *)
let print_litteral l =
if l.negation then
printf "n";
printf "%d" l.id
;;
let rec print_clause cl =
match cl with
|[] -> ()
|head::[] -> print_litteral head
|head::tail ->
print_litteral head;
printf " \\/ ";
print_clause tail
;;
let rec print_CNF f =
match f with
|[] -> ()
|head::[] ->
printf "(";
print_clause head;
printf ")"
|head::tail ->
printf "(";
print_clause head;
printf ") /\\ ";
print_CNF tail
;;
let print_model m level =
let rec aux m =
match m with
|[] -> ()
|l::ls ->
printf "(";
if l.var.negation = true then print_string "n";
print_int l.var.id;
if l.inferred = false then print_string "@";
printf ", %d) " (LevelMap.find l.var.id level);
aux ls
in
aux m;
print_newline ()
;;
let rec print_eq l =
match l with
|[] -> printf "\n"
|(Eq(x, y))::tail -> printf "%d = %d | " x y; print_eq tail
|(Not_eq(x, y))::tail -> printf "%d <> %d | " x y; print_eq tail
;;
(**************************)
(* 3SAT CNF reader *)
let read_SAT str =
let ci = open_in str in
let header = ref (input_line ci) in
while (!header).[0] = 'c' do
header := input_line ci
done;
let nb_var, nb_cl = sscanf (!header) "p cnf %d %d " (fun x y -> x, y) in
let out = ref [] in
for i = 1 to nb_cl do
let cl = ref [] in
let l = ref (fscanf ci " %d " (fun x -> x)) in
while !l <> 0 do
cl := ({id = abs !l; negation = !l < 0})::(!cl);
l := fscanf ci " %d " (fun x -> x)
done;
out := (!cl)::(!out)
done;
close_in ci;
!out
;;
(* Preprocessing of AST *)
let order_ast f =
let rec aux c out =
match c with
|[] -> out
|head::tail -> begin
match head with
|Equal(x, y) -> aux tail ((if x < y then Eq(x, y) else Eq(y, x))::out)
|Different(x, y) -> aux tail ((if x < y then Not_eq(x, y) else Not_eq(y, x))::out)
end
in
List.filter (fun l -> l <> []) (List.map (fun c -> aux c []) f.clause_l) (*hack to solve the problem of the parser*)
;;
let rec tab_find tab x i =
if tab.(i) = x then
i
else
tab_find tab x (i+1)
;;
let rec theory_to_CNF c tab_id out =
match c with
|[] -> out
|head::tail -> begin
match head with
|Eq(x, y) ->
let id = tab_find tab_id (x, y) 0 in
theory_to_CNF tail tab_id ({id = id; negation = false}::out)
|Not_eq(x, y) ->
let id = tab_find tab_id (x, y) 0 in
theory_to_CNF tail tab_id ({id = id; negation = true}::out)
end
;;
let to_CNF f =
let rec list_of_litterals c out =
match c with
|[] -> out
|head::tail -> begin
match head with
|Eq(x, y) |Not_eq(x, y) ->
if List.mem (x, y) out then
list_of_litterals tail out
else
list_of_litterals tail ((x, y)::out)
end
in
let tab_id = Array.of_list (List.fold_left (fun out c -> list_of_litterals c out) [] f) in
(List.map (fun c -> theory_to_CNF c tab_id []) f, tab_id)
;;
let model_to_theory m tab_id =
let rec aux m out =
match m with
|[] -> out
|head::tail ->
let x, y = tab_id.(head.var.id) in
if head.var.negation then
aux tail (Not_eq(x, y)::out)
else
aux tail (Eq(x, y)::out)
in
aux m []
;;
(**************************)
(* SAT solver : rules and functions *)
let nb_var_CNF f =
let rec aux f out =
match f with
|[] -> out
|head::tail ->
let tmp = List.fold_left max (-1) (List.map (fun x -> x.id ) head) in
if tmp > out then
aux tail tmp
else
aux tail out
in
aux f (-1)
;;
let negate_litteral l =
{id = l.id; negation = not l.negation}
;;
let rec negate_clause c =
match c with
|[] -> []
|l::ls -> [negate_litteral l]::(negate_clause ls)
;;
let rec is_defined_in_model lit m =
match m with
|[] -> false
|l::ls ->
if l.var.id = lit.id then
true
else is_defined_in_model lit ls
;;
let rec value_of_litteral_in_model l m =
if Hashtbl.mem m l.id then Hashtbl.find m l.id else None
;;
let satisfied_by_model f m =
let hash_m = Hashtbl.create 100 in
List.iter (fun x -> Hashtbl.add hash_m x.var.id (not x.var.negation) ) m;
let litteral_true l =
if Hashtbl.mem hash_m l.id then
begin
if l.negation then not (Hashtbl.find hash_m l.id)
else Hashtbl.find hash_m l.id
end
else false
in
List.for_all (List.exists litteral_true) f
;;
let gen_potential_unit_clause c =
let rec aux u v =
match v with
|[] -> []
|l::ls -> (l,u@ls)::(aux (l::u) ls)
in
aux [] c
;;
let find_unit_clause f m =
try
List.iter (fun (l,c) -> if not(is_defined_in_model l m)
&& satisfied_by_model (negate_clause c) m then
raise (Unit_clause_found (l,c)))
(List.concat (List.map gen_potential_unit_clause f));
None
with Unit_clause_found (l,c) -> Some (l,c)
;;
let find_litteral_undefined m n scores =
let rec aux i out =
if i > n then
List.hd (List.sort (fun x y -> let ind1 = if x.negation then x.id * 2 else x.id in
let ind2 = if y.negation then y.id * 2 else y.id in
-1 * (compare scores.(ind1) scores.(ind2))) out)
else
let new_out = if is_defined_in_model {id = i; negation = true} m then out else {id = i; negation = true}::out in
let new_new_out = if is_defined_in_model {id = i; negation = false} m then out else {id = i; negation = false}::new_out in
aux (i+1) new_new_out
in
aux 1 []
;;
let add_clause_to_CNF backjump_clause f =
let equal_clause c1 c2 = List.for_all (fun x -> List.mem x c2) c1 && List.length c1 = List.length c2 in
if List.exists (fun x -> equal_clause backjump_clause x) f then
f
else
backjump_clause::f
;;
let unsatisfiable_by_model f m =
let hash_m = Hashtbl.create 100 in
List.iter (fun x -> Hashtbl.add hash_m x.var.id (not x.var.negation) ) m;
let litteral_false l =
if Hashtbl.mem hash_m l.id then
begin
if l.negation then Hashtbl.find hash_m l.id
else not (Hashtbl.find hash_m l.id)
end
else false
in
let rec aux f =
match f with
|[] -> None
|head::tail ->
if List.for_all litteral_false head then
Some(head)
else
aux tail
in
aux f
;;
let count_decision_litteral c level curr_level =
List.fold_left (fun tmp l -> if LevelMap.find l.id level = curr_level then 1+tmp else tmp) 0 c
;;
let reorder_backjump_clause c level curr_level =
let rec aux c out =
match c with
|[] -> raise Undefined_behaviour
|head::tail ->
if LevelMap.find head.id level = curr_level then
(head, (List.rev_append tail out))
else
aux tail (head::out)
in
aux c []
;;
let rec is_infered l m =
match m with
|[] -> false
|head::tail ->
if head.var.id = l.id then
head.inferred
else
is_infered l tail
;;
let merge_clause c1 c2 =
let rec aux c1 c2 out =
match c1 with
|[] -> List.rev_append out c2
|head::tail ->
if List.mem head c2 then
aux tail c2 out
else
aux tail c2 (head::out)
in
aux c1 c2 []
;;
let sort_clause cc m =
let rec pos l m out =
match m with
|[] -> -1
|head::tail ->
if head.var.id = l.id then
out
else
pos l tail (out+1)
in
fst (List.split (List.sort (fun x y -> compare (snd x) (snd y)) (List.map (fun l -> (l, pos l m 0)) cc)))
;;
let find_backjump_clause f m cc antecedent level curr_level scores =
let rec aux cc =
if count_decision_litteral cc level curr_level < 2 (* && List.for_all (fun l -> is_defined_in_model l m) cc *) then
reorder_backjump_clause cc level curr_level
else begin
match cc with
|[] -> raise Undefined_behaviour
|head::tail ->
if is_infered head m then begin
let ind = if head.negation then head.id * 2 else head.id in
scores.(ind) <- scores.(ind) + 1;
aux (merge_clause tail (AnteMap.find head.id antecedent))
end
else
aux (List.rev_append (List.rev tail) [head])
end
in
aux (sort_clause cc m)
;;
let find_submodel_backjump c m =
let rec go_to_next_decision m out =
match m with
|[] -> ([], out)
|head::tail ->
if head.inferred then
go_to_next_decision tail (head::out)
else
(m, out)
in
let rec aux m out curr_level =
if unsatisfiable_by_model [c] out <> None then
curr_level, Some(out)
else begin
match m with
|[] -> 0, None
|_ ->
let new_m, new_out = go_to_next_decision (List.tl m) ((List.hd m)::out) in
aux new_m new_out (curr_level+1)
end
in
let init_m, init_out = go_to_next_decision (List.rev m) [] in
if init_m = [] then
0, None
else
aux init_m init_out 0
;;
(**************************)
(* SMT solver *)
let smt_solver f =
let cnf_f, tab_id = to_CNF (order_ast f) in
let nb_var = nb_var_CNF cnf_f in
let scores = Array.make ((nb_var+1)*2) 0 in
let rec aux f m antecedent level curr_level =
if satisfied_by_model f m then begin
let m_theory = model_to_theory m tab_id in
let t = Sys.time () in
let ans, memo, conflict_theory = is_satisfiable_mod_theory empty_memo m_theory in
let u = Sys.time () in
Printf.printf "Execution time: %fs\n" (Sys.time() -. t); print_newline ();
if ans then
true
else begin
let conflict_clause = theory_to_CNF conflict_theory tab_id [] in
let new_litteral, new_conflict = find_backjump_clause f m conflict_clause antecedent level curr_level scores in
let backjump_clause = new_litteral::new_conflict in
let new_f = add_clause_to_CNF backjump_clause f in
List.iter (fun l -> let ind = if l.negation then l.id * 2 else l.id in scores.(ind) <- scores.(ind) + 1) backjump_clause;
match find_submodel_backjump new_conflict m with
|_, None -> false (* UNSAT *)
|new_curr_level, Some(new_model) ->
aux new_f
({var = new_litteral; inferred = true}::new_model)
(AnteMap.add new_litteral.id new_conflict antecedent)
(LevelMap.add new_litteral.id new_curr_level level)
new_curr_level
end
end
else begin
match unsatisfiable_by_model f m with
|Some(conflict_clause) -> begin (* BACKJUMP *)
let new_litteral, new_conflict = find_backjump_clause f m conflict_clause antecedent level curr_level scores in
let backjump_clause = new_litteral::new_conflict in
let new_f = add_clause_to_CNF backjump_clause f in
List.iter (fun l -> let ind = if l.negation then l.id * 2 else l.id in scores.(ind) <- scores.(ind) + 1) backjump_clause;
match find_submodel_backjump new_conflict m with
|_, None -> false (* UNSAT *)
|new_curr_level, Some(new_model) ->
aux new_f
({var = new_litteral; inferred = true}::new_model)
(AnteMap.add new_litteral.id new_conflict antecedent)
(LevelMap.add new_litteral.id new_curr_level level)
new_curr_level
end
|None -> begin
match find_unit_clause f m with
|None -> (* DECIDE *)
let new_var = find_litteral_undefined m nb_var scores in
aux f ({var = new_var; inferred = false}::m) antecedent (LevelMap.add new_var.id (curr_level+1) level) (curr_level+1)
|Some (l,c) -> (* UNIT *)
aux f ({var = l; inferred = true}::m) (AnteMap.add l.id c antecedent) (LevelMap.add l.id curr_level level) curr_level
end
end
in
aux cnf_f [] (AnteMap.empty) (LevelMap.empty) 0
;;
(*
(* Satisfiability modulo theory of a model *)
let _ =
let ex = [Eq(1, 2) ; Eq(2, 5) ; Eq(3, 4) ; Not_eq(2, 4)] in
let ans, memo, conflict = is_satisfiable_mod_theory empty_memo ex in
print_endline (string_of_bool ans);
print_eq conflict *)
(* let rec test str answer i fin out =
if i > fin then
out
else
let f = read_SAT (str ^ (string_of_int i) ^ ".cnf") in
printf "Test numero %d : " i; flush stdout;
if sat_solver_backjump f = answer then begin
printf "OK\n"; flush stdout;
test str answer (i+1) fin (out+1)
end
else begin
printf "FAIL\n"; flush stdout;
test str answer (i+1) fin out
end
;;
let _ =
printf "Test Positif 50 variables : \n"; flush stdout;
let tmp = test "test1/50_yes_" true 1 16 0 in
printf "%d/16 tests reussis\n" tmp; flush stdout;
printf "Test Negatif 50 variables : \n"; flush stdout;
let tmp = test "test1/50_no_" false 1 8 0 in
printf "%d/8 tests reussis\n" tmp; flush stdout;
printf "Test Positif 100 variables : \n"; flush stdout;
let tmp = test "test1/100_yes_" true 1 16 0 in
printf "%d/16 tests reussis\n" tmp; flush stdout;
printf "Test Negatif 100 variables : \n"; flush stdout;
let tmp = test "test1/100_no_" false 1 8 0 in
printf "%d/8 tests reussis\n" tmp; flush stdout;
printf "Test Positif 200 variables : \n"; flush stdout;
let tmp = test "test1/200_yes_" true 1 16 0 in
printf "%d/16 tests reussis\n" tmp; flush stdout;
printf "Test Negatif 200 variables : \n"; flush stdout;
let tmp = test "test1/200_no_" false 1 8 0 in
printf "%d/8 tests reussis\n" tmp; flush stdout;
;; *)