# Annexe SAT : encodage d'Arrow en CNF et resolution par pysat (axe lib-vs-lib)
from itertools import permutations, combinations, product
from time import perf_counter
from pysat.solvers import Glucose3, Minisat22, Cadical103
def arrow_cnf(alternatives, n_voters):
"""Encode 'existe-t-il une SWF quelconque Pareto + IIA + non-dictature ?' en CNF.
Litteral v[(P, (x, y))] = 'le resultat social classe x avant y sur le profil P'.
"""
orders = list(permutations(alternatives))
profiles = list(product(orders, repeat=n_voters))
pairs = [(x, y) for x in alternatives for y in alternatives if x != y]
lits = {}
for pi in range(len(profiles)):
for xy in pairs:
lits[(pi, xy)] = len(lits) + 1
cnf = []
familles = {"totalite + asymetrie": 0, "transitivite": 0,
"pareto": 0, "iia": 0, "non-dictature": 0}
for pi, prof in enumerate(profiles):
for x, y in combinations(alternatives, 2): # ordre total strict
cnf.append([lits[(pi, (x, y))], lits[(pi, (y, x))]])
cnf.append([-lits[(pi, (x, y))], -lits[(pi, (y, x))]])
familles["totalite + asymetrie"] += 2
for x, y, z in permutations(alternatives, 3): # transitivite
cnf.append([-lits[(pi, (x, y))], -lits[(pi, (y, z))],
lits[(pi, (x, z))]])
familles["transitivite"] += 1
for x, y in pairs: # pareto (tous preferent x a y)
if all(list(p).index(x) < list(p).index(y) for p in prof):
cnf.append([lits[(pi, (x, y))]])
familles["pareto"] += 1
for p1 in range(len(profiles)): # IIA
for p2 in range(p1 + 1, len(profiles)):
for x, y in pairs:
accord = all(
(list(a).index(x) < list(a).index(y))
== (list(b).index(x) < list(b).index(y))
for a, b in zip(profiles[p1], profiles[p2])
)
if accord:
cnf.append([-lits[(p1, (x, y))], lits[(p2, (x, y))]])
cnf.append([lits[(p1, (x, y))], -lits[(p2, (x, y))]])
familles["iia"] += 2
for i in range(n_voters): # non-dictature
cnf.append([-lits[(pi, (x, y))] for pi, prof in enumerate(profiles)
for x, y in pairs
if list(prof[i]).index(x) < list(prof[i]).index(y)])
familles["non-dictature"] += 1
return cnf, lits, profiles, familles
print("ANNEXE SAT : Arrow par solveur -- existence d'une SWF quelconque")
print("=" * 62)
cnf_arrow, lits_arrow, profiles_arrow, familles = arrow_cnf(
alternatives_3, n_voters_brute)
print(f"Instance : {len(profiles_arrow)} profils, {len(lits_arrow)} litteraux, "
f"{len(cnf_arrow)} clauses")
for nom, n in familles.items():
print(f" {nom:<20}: {n}")
print()
for nom, solveur in [("Glucose3", Glucose3), ("MiniSat22", Minisat22),
("CaDiCaL103", Cadical103)]:
t0 = perf_counter()
with solveur(bootstrap_with=cnf_arrow) as s:
est_sat = s.solve()
dt_ms = (perf_counter() - t0) * 1000.0
verdict = "SAT (contredit Arrow !)" if est_sat else "UNSAT (Arrow verifie)"
print(f" {nom:<11}: {verdict} [{dt_ms:7.1f} ms]")
# Frontiere : avec 2 alternatives, le meme encodage devient SAT (theoreme de May)
cnf_2alt, lits_2alt, profiles_2alt, _ = arrow_cnf(['A', 'B'], n_voters_brute)
with Glucose3(bootstrap_with=cnf_2alt) as s:
est_sat_2alt = s.solve()
modele = set(s.get_model())
print()
print(f"Frontiere |A| = 2 : {'SAT' if est_sat_2alt else 'UNSAT'} "
f"-- l'impossibilite ne nait qu'avec la 3e alternative")
if est_sat_2alt:
print("(avis des 3 votants) -> social (modele Glucose3)")
for pi, prof in enumerate(profiles_2alt):
avis = ["A>B" if list(p).index('A') < list(p).index('B') else "B>A"
for p in prof]
social = "A>B" if lits_2alt[(pi, ('A', 'B'))] in modele else "B>A"
print(f" {' '.join(avis):<11} -> {social}")
# La regle de majorite est-elle ELLE-MEME un modele de la CNF ?
def clause_satisfaite(cl, vrais):
return any((l > 0 and l in vrais) or (l < 0 and -l not in vrais)
for l in cl)
modele_maj = set()
for pi, prof in enumerate(profiles_2alt):
n_ab = sum(1 for p in prof if list(p).index('A') < list(p).index('B'))
xy = ('A', 'B') if n_ab >= 2 else ('B', 'A')
modele_maj.add(lits_2alt[(pi, xy)])
maj_valide = all(clause_satisfaite(cl, modele_maj) for cl in cnf_2alt)
print()
print(f"Verification directe : la majorite satisfait-elle toute la CNF ? "
f"{'OUI' if maj_valide else 'NON'}")
print()
print(">>> UNSAT pour |A| = 3 : aucune SWF ne satisfait les trois axiomes.")
print(">>> SAT pour |A| = 2 : des SWF valides existent -- la majorite en est une")
print(" (theoreme de May, 1952) ; le modele rendu par Glucose3 en est une autre")
print(" (biaisee vers A) : existence n'est pas unicite.")