1 :- module(sat_symmetry_breaking, [get_symmetry_breaking_predicates/3]).
2
3 :- use_module(library(lists)).
4 :- use_module(library(samsort)).
5 :- use_module(extension('bliss/bliss_interface')).
6 :- use_module(cdclt_solver(cdclt_pred_to_sat),[next_sat_var_name/1]).
7
8 % Foundation: "Symmetry-breaking predicates for search problems" by Crawford et al.
9 % Improvement: "Solving Difficult Instances of Boolean Satisfiability in the Presence of Symmetry" by Aloul et al.
10
11 %% get_symmetry_breaking_predicates(+CNF, -SBPs, -NewSatVars).
12 get_symmetry_breaking_predicates(CNF, SBPs, NewSatVars) :-
13 bliss_interface:init_bliss_interface,
14 bliss_interface:init_undirected_graph,
15 cnf_to_colored_graph(CNF),
16 bliss_interface:find_automorphisms(Generators),
17 nl, write('Generators: '), write(Generators),nl,
18 get_sbps_from_generators(Generators, TSBPs, NewSatVars),
19 !,
20 SBPs = TSBPs.
21
22 get_sbps_from_generators(Generators, SBPs, NewSatVars) :-
23 add_sbps_from_generators(Generators, [], SBPs, [], NewSatVars).
24
25 add_sbps_from_generators([], Acc, Acc, SAcc, SAcc).
26 add_sbps_from_generators([Generator|T], Acc, SBPs, SAcc, NewSatVars) :-
27 add_sbps_from_generator(Generator, Acc , NAcc, SAcc, NSAcc),
28 add_sbps_from_generators(T, NAcc, SBPs, NSAcc, NewSatVars).
29
30 get_sbps_from_cycle([Id1,Id2], SBPConj) :-
31 % not(Id1) or Id2
32 get_clause_from_node_id_atom(Id1, Clause1),
33 get_clause_from_node_id_atom(Id2, Clause2),
34 negate_clause_to_conj(Clause1, NegConj1),
35 ( NegConj1 = [Clause2]
36 -> SBPConj = [Clause2]
37 ; maplist(append(Clause2), NegConj1, SBPConj)
38 ).
39 get_sbps_from_cycle([Id1,Id2,Id3], SBPConj) :-
40 get_sbps_from_cycle([Id1,Id2], SBPConj1),
41 get_sbps_from_cycle([Id2,Id3], SBPConj2),
42 append(SBPConj1, SBPConj2, SBPConj).
43
44 % only considers two-cycles and three-cycles
45 add_sbps_from_generator([[Id1,Id2]], Acc , NAcc, SAcc, SAcc) :-
46 get_sbps_from_cycle([Id1,Id2], SBPConj),
47 append(SBPConj, Acc, NAcc).
48 add_sbps_from_generator([[Id1,Id2,Id3]], Acc , NAcc, SAcc, SAcc) :-
49 add_sbps_from_generator([[Id1,Id2]], Acc, NAcc1, _, _),
50 add_sbps_from_generator([[Id2,Id3]], NAcc1, NAcc, _, _).
51 add_sbps_from_generator([Cycle1,Cycle2|T], Acc, NAcc, SAcc, NSAcc) :-
52 % e.g., (ab)(cd)(ef) -> (not(a) or b) & (a=b)=>(not(c) or d) & ((a=b) & (c=d))=>(not(e) or f)
53 get_equality_from_cycle(Cycle1, Acc, NAcc1, SAcc, SAcc1, NegEqNodeTriple),
54 add_sbps_from_generator([Cycle1], NAcc1, NAcc2, _, _),
55 add_sbps_from_generator_multi([Cycle2|T], [NegEqNodeTriple], NAcc2, NAcc, SAcc1, NSAcc).
56
57 add_sbps_from_generator_multi([], _, NAcc, NAcc, SAcc, SAcc).
58 add_sbps_from_generator_multi([Cycle|T], NegEqNodeTriples, Acc, NAcc, SAcc, NSAcc) :-
59 get_sbps_from_cycle(Cycle, SBPsCycle),
60 % not(EqNodeTriples) or SBPsCycle
61 maplist(append(NegEqNodeTriples), SBPsCycle, ImplCNF),
62 append(ImplCNF, Acc, NAcc1),
63 ( T == []
64 -> NNegEqNodeTriples = NegEqNodeTriples,
65 NAcc2 = NAcc1,
66 SAcc1 = SAcc
67 ; get_equality_from_cycle(Cycle, NAcc1, NAcc2, SAcc, SAcc1, NegEqNodeTriple),
68 NNegEqNodeTriples = [NegEqNodeTriple|NegEqNodeTriples]
69 ),
70 add_sbps_from_generator_multi(T, NNegEqNodeTriples, NAcc2, NAcc, SAcc1, NSAcc).
71
72 get_equality_from_cycle(Cycle, Acc, NAcc, SAcc, [SatVarName-EqClauses|SAcc], NegEqNodeTriple) :-
73 next_sat_var_name(SatVarName),
74 PosEqNodeTriple = pred_true-_-SatVarName,
75 NegEqNodeTriple = pred_false-_-SatVarName,
76 get_equality_from_cycle_cnf(Cycle, PosEqNodeTriple, NegEqNodeTriple, EqCNF),
77 get_clauses_from_bliss_cycle(Cycle, EqClauses),
78 append(EqCNF, Acc, NAcc).
79
80 get_clauses_from_bliss_cycle(Cycle, EqSatVarNames) :-
81 get_clauses_from_bliss_cycle(Cycle, [], EqSatVarNames).
82
83 get_clauses_from_bliss_cycle([], Acc, Acc).
84 get_clauses_from_bliss_cycle([IdA|T], Acc, EqSatVarNames) :-
85 get_clause_from_node_id_atom(IdA, Clause),
86 get_clauses_from_bliss_cycle(T, [Clause|Acc], EqSatVarNames).
87
88 get_equality_from_cycle_cnf([Id1,Id2], PosEqNodeTriple, NegEqNodeTriple, EqCNF) :-
89 % the new SAT variable eq is equivalent to the equality of Id1 & Id2
90 % note that non unit clauses might be referenced to ids which requires some rewriting
91 disjunct_ids_to_cnf(Id1, not(Id2), TCNF1),
92 maplist(append([NegEqNodeTriple]), TCNF1, CNF1),
93 disjunct_ids_to_cnf(not(Id1), Id2, TCNF2),
94 maplist(append([NegEqNodeTriple]), TCNF2, CNF2),
95 append(CNF1, CNF2, CNFLhsImpl),
96 disjunct_ids_to_cnf(not(Id1), not(Id2), TCNF3),
97 maplist(append([PosEqNodeTriple]), TCNF3, CNF3),
98 disjunct_ids_to_cnf(Id1, Id2, TCNF4),
99 maplist(append([PosEqNodeTriple]), TCNF4, CNF4),
100 append(CNF3, CNF4, CNFRhsImpl),
101 append(CNFLhsImpl, CNFRhsImpl, EqCNF). % TO DO: falsity of eq
102 get_equality_from_cycle_cnf([Id1,Id2,Id3], PosEqNodeTriple, NegEqNodeTriple, EqCNF) :-
103 % the new SAT variable eq is equivalent to the equality of Id1 & Id2 & Id3
104 disjunct_ids_to_cnf(not(Id1), Id3, TCNF1),
105 maplist(append([NegEqNodeTriple]), TCNF1, CNF1),
106 disjunct_ids_to_cnf(Id1, not(Id2), TCNF2),
107 maplist(append([NegEqNodeTriple]), TCNF2, CNF2),
108 disjunct_ids_to_cnf(Id2, not(Id3), TCNF3),
109 maplist(append([NegEqNodeTriple]), TCNF3, CNF3),
110 append([CNF1,CNF2,CNF3], CNFLhsImpl),
111 disjunct_ids_to_cnf(not(Id1), not(Id2), TCNF4),
112 maplist(append([PosEqNodeTriple]), TCNF4, TTCNF4),
113 get_clause_from_node_id_atom(Id3, Clause3),
114 negate_clause_to_conj(Clause3, NegConj3),
115 append(TTCNF4, NegConj3, CNF4),
116 disjunct_ids_to_cnf(Id1, Id2, TCNF5),
117 maplist(append([PosEqNodeTriple]), TCNF5, TTCNF5),
118 maplist(append(Clause3), TTCNF5, CNF5),
119 append(CNF4, CNF5, CNFRhsImpl),
120 append(CNFLhsImpl, CNFRhsImpl, EqCNF).
121
122 disjunct_ids_to_cnf(not(Id1), not(Id2), CNF) :-
123 !,
124 get_clause_from_node_id_atom(Id1, Clause1),
125 get_clause_from_node_id_atom(Id2, Clause2),
126 negate_clause_to_conj(Clause1, Id1Conj),
127 negate_clause_to_conj(Clause2, Id2Conj),
128 disjunct_cnfs_to_cnf(Id1Conj, Id2Conj, CNF).
129 disjunct_ids_to_cnf(not(Id1), Id2, CNF) :-
130 !,
131 get_clause_from_node_id_atom(Id1, Clause1),
132 get_clause_from_node_id_atom(Id2, Clause2),
133 negate_clause_to_conj(Clause1, Id1Conj),
134 maplist(append(Clause2), Id1Conj, CNF).
135 disjunct_ids_to_cnf(Id1, not(Id2), CNF) :-
136 !,
137 get_clause_from_node_id_atom(Id1, Clause1),
138 get_clause_from_node_id_atom(Id2, Clause2),
139 negate_clause_to_conj(Clause2, Id2Conj),
140 maplist(append(Clause1), Id2Conj, CNF).
141 disjunct_ids_to_cnf(Id1, Id2, CNF) :-
142 get_clause_from_node_id_atom(Id1, Clause1),
143 get_clause_from_node_id_atom(Id2, Clause2),
144 append(Clause1, Clause2, NewClause),
145 CNF = [NewClause].
146
147 disjunct_cnfs_to_cnf(Id1Conj, [], Id1Conj) :- !.
148 disjunct_cnfs_to_cnf([], Id2Conj, Id2Conj) :- !.
149 disjunct_cnfs_to_cnf(Id1Conj, Id2Conj, CNF) :-
150 disjunct_cnfs_to_cnf_acc(Id1Conj, Id2Conj, [], CNF).
151
152 disjunct_cnfs_to_cnf_acc([], _, Acc, Acc).
153 disjunct_cnfs_to_cnf_acc([Clause1|T1], Id2Conj, Acc, CNF) :-
154 maplist(append(Clause1), Id2Conj, TConj),
155 append(TConj, Acc, NAcc),
156 disjunct_cnfs_to_cnf_acc(T1, Id2Conj, NAcc, CNF).
157
158 negate_clause_to_conj(Clause, NegConj) :-
159 negate_clause_to_conj(Clause, [], NegConj).
160
161 negate_clause_to_conj([], Acc, Acc).
162 negate_clause_to_conj([Pol-_-SatVarName|T], Acc, NegConj) :-
163 negate_polarity(Pol, NegPol),
164 negate_clause_to_conj(T, [[NegPol-_-SatVarName]|Acc], NegConj).
165
166 negate_polarity(pred_true, pred_false).
167 negate_polarity(pred_false, pred_true).
168
169 get_clause_from_node_id_atom(IdAtom, Clause) :-
170 number_to_atom(NId, IdAtom),
171 get_clause_from_node_id(NId, Clause).
172
173 get_clause_from_node_id(Id, Clause) :-
174 sat_clause(Id, TClause),
175 !,
176 Clause = TClause.
177 get_clause_from_node_id(Id, Clause) :-
178 seen(SatVarName,Pol,Id), % TO DO: improve search by Id
179 Clause = [Pol-_-SatVarName].
180
181 :- dynamic seen/3, sat_clause/2.
182 :- volatile seen/3, sat_clause/2.
183
184 cnf_to_colored_graph(CNF) :-
185 retractall(seen(_,_,_)),
186 retractall(sat_clause(_,_)),
187 cnf_to_colored_graph_init(CNF).
188
189 lit_compare(_-_-A1, _-_-A2) :- A1@<A2.
190
191 cnf_to_colored_graph_init([]).
192 cnf_to_colored_graph_init([Clause|T]) :-
193 samsort(lit_compare, Clause, SClause),
194 \+ sat_clause(_, SClause),
195 clause_to_colored_graph(SClause),
196 cnf_to_colored_graph_init(T).
197 cnf_to_colored_graph_init([_|T]) :-
198 cnf_to_colored_graph_init(T).
199
200 %% clause_to_colored_graph(+Clause, +ClauseBlissId).
201 % Removes true clauses and false literals.
202 clause_to_colored_graph([Pol-Pol-_]) :-
203 % CNF has already been unit propagated so all unit clauses are true
204 !.
205 clause_to_colored_graph([Pol1-Var1-Lit1,Pol2-Var2-Lit2]) :-
206 ( (Pol1 == Var1; Pol2 == Var2)
207 -> true
208 ; % edge without clause node for binary clause
209 register_var_node(Lit1, Lit1IdPos, Lit1IdNeg),
210 register_var_node(Lit2, Lit2IdPos, Lit2IdNeg),
211 ( Pol1 == pred_true
212 -> ELit1 = Lit1IdPos
213 ; ELit1 = Lit1IdNeg
214 ),
215 ( Pol2 == pred_true
216 -> ELit2 = Lit2IdPos
217 ; ELit2 = Lit2IdNeg
218 ),
219 bliss_interface:add_edge(ELit1, ELit2)
220 ),
221 !.
222 clause_to_colored_graph(Clause) :-
223 member(Pol-Var-_, Clause),
224 Pol == Var,
225 !.
226 clause_to_colored_graph(Clause) :-
227 Clause \= [_],
228 bliss_interface:add_node(1, ClauseBlissId),
229 asserta(sat_clause(ClauseBlissId,Clause)),
230 clause_to_colored_graph_non_binary(Clause, ClauseBlissId).
231
232 clause_to_colored_graph_non_binary([], _).
233 clause_to_colored_graph_non_binary([Pol-Var-Lit|T], ClauseBlissId) :-
234 negate_polarity(Pol, NegPol),
235 Var \== NegPol, % remove false literals
236 !,
237 register_var_node(Lit, LitIdPos, LitIdNeg),
238 ( Pol == pred_true
239 -> ELit = LitIdPos
240 ; ELit = LitIdNeg
241 ),
242 bliss_interface:add_edge(ELit, ClauseBlissId),
243 clause_to_colored_graph_non_binary(T, ClauseBlissId).
244 clause_to_colored_graph_non_binary([_|T], ClauseBlissId) :-
245 clause_to_colored_graph_non_binary(T, ClauseBlissId).
246
247 %% register_var_node(+Lit, -LitIdPos, -LitIdNeg).
248 % Two nodes for a SAT variable for the two polarities and one edge between both nodes.
249 register_var_node(Lit, LitIdPos, LitIdNeg) :-
250 seen(Lit,pred_true,TLitIdPos),
251 seen(Lit,pred_false,TLitIdNeg),
252 !,
253 LitIdPos = TLitIdPos,
254 LitIdNeg = TLitIdNeg.
255 register_var_node(Lit, LitIdPos, LitIdNeg) :-
256 bliss_interface:add_node(2, LitIdPos),
257 bliss_interface:add_node(2, LitIdNeg),
258 asserta(seen(Lit,pred_true,LitIdPos)),
259 asserta(seen(Lit,pred_false,LitIdNeg)),
260 bliss_interface:add_edge(LitIdPos, LitIdNeg).
261
262 number_to_atom(Number, Atom) :-
263 number(Number),
264 number_codes(Number, Codes),
265 atom_codes(Atom, Codes),!.
266 number_to_atom(Number, Atom) :-
267 atom(Atom),
268 atom_codes(Atom, Codes),
269 number_codes(Number, Codes),!.