| 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),!. |