SocialChoice 04 : Agregation Computationnelle - SAT et DPLL (twin C# .NET)

Twin C# .NET de GameTheory/SocialChoice/04-Computational-Aggregation-SAT-Z3.ipynb (Python, PySAT + Z3). Marathon #4956 (jumeaux .NET <-> Python), Prong B (#3801) : solveur SAT DPLL from-scratch (BCL .NET 9, 0 NuGet), coherent avec 01-Arrow-Csharp et 03-Voting-Methods-Csharp.

Idee centrale. Un problème SAT demande s’il existe une affectation de variables booleennes satisfaisant une formule en forme normale conjonctive (CNF). On encode le theoreme d’Arrow comme un problème SAT : si l’encodage est UNSAT, alors aucune fonction de bien-etre social ne satisfait simultanement Pareto, IIA et non-dictature - le theoreme est mecaniquement prouve.

Choix du solveur (Prong B). Le notebook Python s’appuie sur PySAT (Glucose3, MiniSat22, CaDiCaL103 - solveurs C++ industriels avec clause learning CDCL). Ce twin C# implemente a la place un solveur DPLL from-scratch (Davis-Putnam-Logemann-Loveland avec unit propagation) en C# pur. DPLL est sound et complet pour SAT : il trouve un modèle si et seulement si la formule est satisfiable. C’est l’algorithme canonique des solveurs SAT modernes (dont derive CDCL). Verdict SOTA : SOTA-OK (algorithme reel et complet), avec une limitation de scaling honnetement documentee (DPLL sans clause-learning est plus lent que Glucose3 sur les grandes instances - c’est précisément la raison d’etre des solveurs industriels que PySAT wrap). La Section 8 comble cette limite par un pont PythonNet vers PySAT : les trois solveurs CDCL du twin Python (Glucose3, MiniSat22, CaDiCaL103) s’executent sur l’encodage C# — en plus du DPLL from-scratch, jamais a sa place.

0. Configuration

Les trois setters de culture (InvariantCulture) sont requis pour la persistance cross-cell (con 01-Arrow-Csharp).

// SocialChoice 04 : SAT et DPLL -- twin C# de 04-Computational-Aggregation-SAT-Z3
// Prong B (#3801) : solveur DPLL from-scratch (BCL .NET 9, 0 NuGet).
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;

Console.WriteLine("Configuration OK : SocialChoice 04 - SAT et DPLL (twin C# .NET)");
Configuration OK : SocialChoice 04 - SAT et DPLL (twin C# .NET)

1. Rappels : Logique Propositionnelle et SAT

Un problème SAT demande s’il existe une affectation de variables booleennes qui rend vraie toutes les clauses d’une formule CNF (forme normale conjonctive).

  • Literal : une variable \(x_i\) (positif) ou sa negation \(\neg x_i\) (negatif, note \(-i\)).
  • Clause : disjonction (OU) de litteraux, ex. \(x_1 \lor \neg x_2 \lor x_3\).
  • CNF : conjonction (ET) de clauses.

Résultat : SAT (il existe un modèle) ou UNSAT (aucun modèle). Pour les theoremes d’impossibilite, UNSAT = theoreme prouve (aucun objet ne satisfait les axiomes).

1.1 Solveur DPLL from-scratch

Le solveur implemente l’algorithme DPLL avec unit propagation :

  1. Unit propagation : si une clause n’a qu’un seul literal non assigne (et n’est pas encore satisfaite), on force ce literal a vrai. On repete jusqu’a fixpoint.
  2. Detection de conflit : si une clause devient vide (tous ses litteraux falsifies), la branche courante est UNSAT.
  3. Branchement : on choisit une variable non assignee, on essaie true puis false (backtracking).

DPLL est complet : il explore tout l’arbre de recherche (elague par unit propagation) et ne rate aucun modèle.

// === Solveur SAT DPLL from-scratch (Prong B #3801) ===
// CNF : liste de clauses ; chaque clause = liste de litteraux (int, +var ou -var).
// Variables indexees a partir de 1.

public static class DpllSolver
{
    // Evalue un literal sous une affectation : true (satisfait), false (falsifie), null (libre).
    static bool? LitValue(int lit, bool?[] assign)
    {
        int v = Math.Abs(lit);
        if (!assign[v].HasValue) return null;
        return lit > 0 ? assign[v].Value : !assign[v].Value;
    }

    // Propagation d'unites jusqu'a fixpoint. Retourne false si conflit.
    static bool UnitPropagate(List<List<int>> cnf, bool?[] assign)
    {
        bool changed = true;
        while (changed)
        {
            changed = false;
            foreach (var clause in cnf)
            {
                int unassigned = 0;
                int unitLit = 0;
                bool satisfied = false;
                foreach (int lit in clause)
                {
                    bool? val = LitValue(lit, assign);
                    if (val == true) { satisfied = true; break; }
                    if (val == null) { unassigned++; unitLit = lit; }
                }
                if (satisfied) continue;
                if (unassigned == 0) return false;          // clause vide = conflit
                if (unassigned == 1)                         // clause unitaire : forcer
                {
                    int v = Math.Abs(unitLit);
                    assign[v] = unitLit > 0;
                    changed = true;
                }
            }
        }
        return true;
    }

    // Choisit une variable libre (premiere trouvee dans la premiere clause non satisfaite).
    static int PickVar(List<List<int>> cnf, bool?[] assign)
    {
        foreach (var clause in cnf)
        {
            foreach (int lit in clause)
            {
                int v = Math.Abs(lit);
                if (!assign[v].HasValue) return v;
            }
        }
        return 0;
    }

    static bool AllSatisfied(List<List<int>> cnf, bool?[] assign)
    {
        foreach (var clause in cnf)
        {
            bool sat = false;
            foreach (int lit in clause)
                if (LitValue(lit, assign) == true) { sat = true; break; }
            if (!sat) return false;
        }
        return true;
    }

    // DPLL recursif. Retourne true si SAT (modele dans assign).
    static bool Dpll(List<List<int>> cnf, bool?[] assign)
    {
        if (!UnitPropagate(cnf, assign)) return false;
        if (AllSatisfied(cnf, assign)) return true;
        int v = PickVar(cnf, assign);
        if (v == 0) return false;

        // Branche true
        var snapshot = (bool?[])assign.Clone();
        assign[v] = true;
        if (Dpll(cnf, assign)) return true;
        Array.Copy(snapshot, assign, assign.Length);

        // Branche false
        assign[v] = false;
        return Dpll(cnf, assign);
    }

    // API publique. Retourne (sat, model).
    public static (bool sat, Dictionary<int,bool> model) Solve(List<List<int>> cnf, int nVars)
    {
        var assign = new bool?[nVars + 1];
        if (Dpll(cnf, assign))
        {
            var model = new Dictionary<int,bool>();
            for (int v = 1; v <= nVars; v++)
                if (assign[v].HasValue) model[v] = assign[v].Value;
            return (true, model);
        }
        return (false, null);
    }
}

1.2 Premier exemple : verification SAT

Testons le solveur DPLL sur une formule CNF simple :

\[(x_1 \lor x_2) \land (\neg x_1 \lor x_3) \land (\neg x_2 \lor \neg x_3)\]

On s’attend a SAT (plusieurs modèles satisfont la formule).

// Exemple simple : verification SAT
var cnf = new List<List<int>>
{
    new(){ 1, 2 },        // x1 OR x2
    new(){ -1, 3 },       // NOT x1 OR x3
    new(){ -2, -3 }       // NOT x2 OR NOT x3
};
int nVars = 3;

var (sat, model) = DpllSolver.Solve(cnf, nVars);

Console.WriteLine("Formule : (x1 v x2) ^ (~x1 v x3) ^ (~x2 v ~x3)");
Console.WriteLine($"Resultat : {(sat ? "SAT" : "UNSAT")}");
if (sat)
{
    Console.Write("Modele : ");
    Console.WriteLine(string.Join(", ", model.OrderBy(kv => kv.Key).Select(kv => $"x{kv.Key}={kv.Value}")));
}

// Verification manuelle : enumeration de tous les modeles possibles
Console.WriteLine();
Console.WriteLine("Verification des modeles possibles :");
foreach (var (x1, x2, x3) in from a in new[]{false,true} from b in new[]{false,true} from c in new[]{false,true} select (a,b,c))
{
    bool c1 = x1 || x2;
    bool c2 = (!x1) || x3;
    bool c3 = (!x2) || (!x3);
    if (c1 && c2 && c3)
        Console.WriteLine($"  x1={x1}, x2={x2}, x3={x3} : SATISFAIT");
}
Formule : (x1 v x2) ^ (~x1 v x3) ^ (~x2 v ~x3)
Resultat : SAT
Modele : x1=True, x2=False, x3=True

Verification des modeles possibles :
  x1=False, x2=True, x3=False : SATISFAIT
  x1=True, x2=False, x3=True : SATISFAIT

Interpretation. Le solveur DPLL trouve un modèle satisfaisant. L’enumeration manuelle confirme qu’il existe plusieurs affectations valides (par exemple \(x_1=F, x_2=T, x_3=F\)).

1.3 Exemple UNSAT

Une formule contradictoire doit retourner UNSAT. Par exemple : \((x_1) \land (\neg x_1)\) - \(x_1\) ne peut pas etre simultanement vrai et faux.

// Exemple UNSAT : x1 AND NOT x1
var cnfUnsat = new List<List<int>>
{
    new(){ 1 },     // x1
    new(){ -1 }     // NOT x1
};
var (sat2, model2) = DpllSolver.Solve(cnfUnsat, 1);
Console.WriteLine($"Formule : (x1) ^ (~x1)");
Console.WriteLine($"Resultat : {(sat2 ? "SAT" : "UNSAT")}");
Console.WriteLine("L'unite propagation force x1=true (clause 1) puis detecte le conflit (clause 2 falsifiee).");
Formule : (x1) ^ (~x1)
Resultat : UNSAT
L'unite propagation force x1=true (clause 1) puis detecte le conflit (clause 2 falsifiee).

2. Encodage SAT du theoreme d’Arrow

Theoreme d’Arrow (1951). Avec \(|A| \geq 3\) alternatives et \(n \geq 2\) votants, il n’existe aucune fonction de bien-etre social (SWF) satisfaisant simultanement :

  1. Pareto : si tous preferent \(x \succ y\), le social aussi ;
  2. IIA (Indépendance aux alternatives non pertinentes) : le classement social entre \(x\) et \(y\) ne depend que des préférences individuelles entre \(x\) et \(y\) ;
  3. Non-dictature : aucun votant n’impose toujours son classement.

Variables booleennes : \(r[\pi, x, y]\) = vraie signifie “pour le profil \(\pi\), la societe prefere \(x\) a \(y\)”. L’encodage explore tous les profils possibles (domaine universel) et encode chaque axiome comme un ensemble de clauses CNF.

// === Encodeur SAT du theoreme d'Arrow (port C# de ArrowSATEncoder Python) ===
// Genere les clauses CNF pour : totalite, asymetrie, transitivite, Pareto, IIA, non-dictature.

public static IEnumerable<IEnumerable<T>> Permutations<T>(IEnumerable<T> elements, int k)
{
    var list = elements.ToList();
    if (k == 1) return list.Select(t => new T[]{t});
    return Permutations(list, k - 1).SelectMany(p => list.Where(e => !p.Contains(e)), (p, e) => p.Append(e));
}

public static List<List<T>> Combinations<T>(List<T> elements, int k)
{
    var result = new List<List<T>>();
    void Rec(int start, List<T> cur)
    {
        if (cur.Count == k) { result.Add(cur.ToList()); return; }
        for (int i = start; i < elements.Count; i++) { cur.Add(elements[i]); Rec(i + 1, cur); cur.RemoveAt(cur.Count - 1); }
    }
    Rec(0, new List<T>());
    return result;
}

public class ArrowSATEncoder
{
    public List<string> Alternatives { get; }
    public int NAlt => Alternatives.Count;
    public int NVoters { get; }
    public List<List<List<string>>> Profiles { get; }

    private readonly Dictionary<(int,string,string), int> _varMap = new();
    private int _varCounter = 0;

    public ArrowSATEncoder(List<string> alternatives, int nVoters)
    {
        Alternatives = alternatives;
        NVoters = nVoters;
        var allOrders = Permutations(alternatives, NAlt).Select(p => p.ToList()).ToList();
        Profiles = new List<List<List<string>>>();
        void Build(int depth, List<List<string>> cur)
        {
            if (depth == nVoters) { Profiles.Add(cur.Select(o => o.ToList()).ToList()); return; }
            foreach (var order in allOrders) { cur.Add(order); Build(depth + 1, cur); cur.RemoveAt(cur.Count - 1); }
        }
        Build(0, new List<List<string>>());
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                _varMap[(pi, x, y)] = ++_varCounter;
    }

    public int GetVar(int pi, string x, string y) => _varMap[(pi, x, y)];

    public List<List<int>> EncodeCompleteness()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var pair in Combinations(Alternatives, 2))
            {
                var (x, y) = (pair[0], pair[1]);
                clauses.Add(new(){ GetVar(pi, x, y), GetVar(pi, y, x) });
            }
        return clauses;
    }

    public List<List<int>> EncodeAsymmetry()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var pair in Combinations(Alternatives, 2))
            {
                var (x, y) = (pair[0], pair[1]);
                clauses.Add(new(){ -GetVar(pi, x, y), -GetVar(pi, y, x) });
            }
        return clauses;
    }

    public List<List<int>> EncodeTransitivity()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var t in Permutations(Alternatives, 3).Select(p => p.ToList()))
            {
                string x = t[0], y = t[1], z = t[2];
                clauses.Add(new(){ -GetVar(pi, x, y), -GetVar(pi, y, z), GetVar(pi, x, z) });
            }
        return clauses;
    }

    public List<List<int>> EncodePareto()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
        {
            var profile = Profiles[pi];
            foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
            {
                bool allPrefer = profile.All(v => v.IndexOf(x) < v.IndexOf(y));
                if (allPrefer) clauses.Add(new(){ GetVar(pi, x, y) });
            }
        }
        return clauses;
    }

    public List<List<int>> EncodeIIA()
    {
        var clauses = new List<List<int>>();
        for (int pi1 = 0; pi1 < Profiles.Count; pi1++)
            for (int pi2 = pi1 + 1; pi2 < Profiles.Count; pi2++)
            {
                var p1 = Profiles[pi1]; var p2 = Profiles[pi2];
                foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                {
                    bool sameXy = p1.Zip(p2, (v1, v2) => (v1.IndexOf(x) < v1.IndexOf(y)) == (v2.IndexOf(x) < v2.IndexOf(y))).All(b => b);
                    if (sameXy)
                    {
                        int v1xy = GetVar(pi1, x, y); int v2xy = GetVar(pi2, x, y);
                        clauses.Add(new(){ -v1xy, v2xy });
                        clauses.Add(new(){ v1xy, -v2xy });
                    }
                }
            }
        return clauses;
    }

    public List<List<int>> EncodeNonDictatorship()
    {
        // Non-dictature (Arrow) : pour chaque electeur i, il existe au moins
        // un couple (profil, paire) ou le classement social contredit i.
        // Une clause DISJONCTIVE PAR ELECTEUR (et non par paire) : sinon on
        // exige que i soit contredit sur CHAQUE paire, ce qui sur-contraint
        // l'encodage et declare faussement UNSAT le cas |A| = 2 -- alors qu'une
        // SWF non dictatoriale (regle majoritaire) y satisfait Pareto + IIA +
        // non-dictature. La frontiere d'impossibilite d'Arrow est |A| >= 3.
        var clauses = new List<List<int>>();
        for (int voter = 0; voter < NVoters; voter++)
        {
            var witness = new List<int>();
            for (int pi = 0; pi < Profiles.Count; pi++)
                foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                    if (Profiles[pi][voter].IndexOf(x) < Profiles[pi][voter].IndexOf(y))
                        witness.Add(-GetVar(pi, x, y));
            if (witness.Count > 0) clauses.Add(witness);
        }
        return clauses;
    }

    public List<List<int>> EncodeAll()
    {
        var all = new List<List<int>>();
        all.AddRange(EncodeCompleteness());
        all.AddRange(EncodeAsymmetry());
        all.AddRange(EncodeTransitivity());
        all.AddRange(EncodePareto());
        all.AddRange(EncodeIIA());
        all.AddRange(EncodeNonDictatorship());
        return all;
    }

    public Dictionary<string,int> Stats() => new()
    {
        ["alternatives"] = NAlt,
        ["voters"] = NVoters,
        ["profiles"] = Profiles.Count,
        ["variables"] = _varCounter,
        ["clauses"] = EncodeAll().Count
    };
}

// Test : encodage pour 3 alternatives, 2 votants
var enc = new ArrowSATEncoder(new List<string>{"A","B","C"}, nVoters: 2);
var statsEnc = enc.Stats();
Console.WriteLine("Encodage Arrow (3 alt, 2 voters)");
Console.WriteLine($"  Profils    : {statsEnc["profiles"]}");
Console.WriteLine($"  Variables  : {statsEnc["variables"]}");
Console.WriteLine($"  Clauses    : {statsEnc["clauses"]}");
Encodage Arrow (3 alt, 2 voters)
  Profils    : 36
  Variables  : 216
  Clauses    : 2216

Interpretation de l’encodage. Pour 3 alternatives et 2 votants, l’encodage genere 216 variables booleennes (36 profils x 6 paires ordonnees) et un peu plus de 2200 clauses reparties sur les 6 familles d’axiomes. C’est un problème SAT de taille modeste, bien dans la portee de DPLL.


3. Verification UNSAT : le theoreme d’Arrow prouve par SAT

Lancons le solveur DPLL sur l’encodage complet. Si le résultat est UNSAT, alors aucune SWF ne satisfait les axiomes : le theoreme d’Arrow est mecaniquement verifie.

// === Verification du theoreme d'Arrow par DPLL ===
// ATTENTION : DPLL sans clause-learning explore un arbre plus large que Glucose3 (PySAT).
// Pour 3 alternatives / 2 votants (216 vars, ~2216 clauses), l'execution peut prendre
// de quelques secondes a quelques minutes selon l'ordre de branchement.

Console.WriteLine("VERIFICATION DU THEOREME D'ARROW PAR DPLL");
Console.WriteLine("=".PadRight(50, '='));
Console.WriteLine($"Configuration : {statsEnc["alternatives"]} alternatives, {statsEnc["voters"]} votants");
Console.WriteLine();

var clausesArrow = enc.EncodeAll();
var watch = System.Diagnostics.Stopwatch.StartNew();
var (satArrow, _) = DpllSolver.Solve(clausesArrow, statsEnc["variables"]);
watch.Stop();

string result = satArrow ? "SAT (contredit Arrow !)" : "UNSAT (Arrow verifie)";
Console.WriteLine($"  DPLL (from-scratch) : {result}");
Console.WriteLine($"    {statsEnc["variables"]} variables, {clausesArrow.Count} clauses, {statsEnc["profiles"]} profils");
Console.WriteLine($"    Temps : {watch.Elapsed.TotalSeconds:F2} s");

Console.WriteLine();
if (!satArrow)
{
    Console.WriteLine("Resultat : UNSAT");
    Console.WriteLine("=> Aucune SWF ne peut satisfaire Pareto + IIA + non-dictature");
    Console.WriteLine("=> Le theoreme d'Arrow est mecaniquement verifie");
}
VERIFICATION DU THEOREME D'ARROW PAR DPLL
==================================================
Configuration : 3 alternatives, 2 votants

  DPLL (from-scratch) : UNSAT (Arrow verifie)
    216 variables, 2216 clauses, 36 profils
    Temps : 0.00 s

Resultat : UNSAT
=> Aucune SWF ne peut satisfaire Pareto + IIA + non-dictature
=> Le theoreme d'Arrow est mecaniquement verifie

Interpretation. DPLL confirme UNSAT : aucune affectation des 216 variables ne satisfait simultanement les 6 familles de clauses. Le theoreme d’Arrow est prouve par exhaustion mecanique (elaguee par unit propagation).

Note de performance (honnete). Le solveur DPLL from-scratch sans clause learning est plus lent que les solveurs industriels (Glucose3, CaDiCaL) que PySAT wrappe cote Python. Pour 3 alternatives, l’ecart reste modeste (secondes). Pour 4+ alternatives, l’explosion combinatoire avantagerait nettement CDCL - c’est précisément la raison d’etre des solveurs industriels. Ce twin garde DPLL pour la transparence pedagogique (algorithme lisible, 0 NuGet) et documente honnetement ce plafond.


4. Cas special : 2 alternatives

Le theoreme d’Arrow classique suppose \(|A| \geq 3\). Que se passe-t-il avec seulement 2 alternatives ? L’encodage retourne SAT : une fonction de bien-etre social non dictatoriale existe (la regle majoritaire satisfait Pareto + IIA + non-dictature des que \(|A| < 3\)). C’est precisement la frontiere du theoreme : l’impossibilite ne tient plus en dessous de 3 alternatives, car l’espace des profils est suffisamment restreint pour qu’aucun electeur ne puisse etre dictateur au sens d’Arrow (IIA impose que le classement social d’une paire ne depende que des preferences individuelles sur cette paire, et comme il n’existe qu’une seule paire, tout reglage coherent Pareto + non-dictature est realisable). L’encodeur SAT et l’encodeur SMT (Z3, section 7) donnent le meme verdict SAT, ce qui confirme la coherence de l’encodage.

// Cas 2 alternatives : SAT (frontiere |A| < 3 du theoreme d'Arrow)
var enc2 = new ArrowSATEncoder(new List<string>{"A","B"}, nVoters: 2);
var stats2 = enc2.Stats();
var clauses2 = enc2.EncodeAll();
var (sat2alt, model2alt) = DpllSolver.Solve(clauses2, stats2["variables"]);

Console.WriteLine("CAS 2 ALTERNATIVES : ANALYSE DE L'ENCODAGE");
Console.WriteLine("=".PadRight(50, '='));
Console.WriteLine($"  Profils    : {stats2["profiles"]}");
Console.WriteLine($"  Variables  : {stats2["variables"]}");
Console.WriteLine($"  Clauses    : {stats2["clauses"]}");
Console.WriteLine($"  Resultat   : {(sat2alt ? "SAT" : "UNSAT")}");
Console.WriteLine();
Console.WriteLine("Analyse :");
if (sat2alt)
{
    Console.WriteLine("  L'encodage SAT trouve SAT pour 2 alternatives.");
    Console.WriteLine("  Une SWF non dictatoriale existe (regle majoritaire).");
    Console.WriteLine("  - Pareto + IIA + non-dictature sont compatibles des que |A| < 3");
    Console.WriteLine("  - IIA : le classement social d'une paire ne depend que des");
    Console.WriteLine("    preferences individuelles sur cette paire (une seule paire ici)");
    Console.WriteLine("  - Aucun electeur ne peut etre dictateur au sens d'Arrow");
    Console.WriteLine();
    Console.WriteLine("  Note : le theoreme d'Arrow classique suppose |A| >= 3.");
    Console.WriteLine("  Le cas |A| = 2 est en dessous de la frontiere d'impossibilite :");
    Console.WriteLine("  la conclusion d'Arrow ne s'applique pas, et SAT est correct.");
}
else
{
    Console.WriteLine("  UNSAT inattendu pour 2 alternatives (frontiere |A| < 3 du theoreme).");
}
CAS 2 ALTERNATIVES : ANALYSE DE L'ENCODAGE
==================================================
  Profils    : 4
  Variables  : 8
  Clauses    : 12
  Resultat   : SAT

Analyse :
  L'encodage SAT trouve SAT pour 2 alternatives.
  Une SWF non dictatoriale existe (regle majoritaire).
  - Pareto + IIA + non-dictature sont compatibles des que |A| < 3
  - IIA : le classement social d'une paire ne depend que des
    preferences individuelles sur cette paire (une seule paire ici)
  - Aucun electeur ne peut etre dictateur au sens d'Arrow

  Note : le theoreme d'Arrow classique suppose |A| >= 3.
  Le cas |A| = 2 est en dessous de la frontiere d'impossibilite :
  la conclusion d'Arrow ne s'applique pas, et SAT est correct.

5. Verdict SOTA (EPIC #3801)

Capacite Twin C# (this) Twin Python Verdict
Resolution SAT (CNF) DPLL from-scratch (unit propagation, backtracking) PySAT (Glucose3, MiniSat22, CaDiCaL103 - CDCL) SOTA-OK (DPLL est sound + complet ; CDCL plus rapide sur grandes instances, documente)
Generation de clauses CNF ArrowSATEncoder from-scratch (port direct) idem SOTA-OK
Encodage theoreme d’Arrow totalite + asymetrie + transitivite + Pareto + IIA + non-dictature idem SOTA-OK
Couverture Z3 (SMT) non portee ce tranche Z3 (cell 2 imports) Tranche 2 (DPLL suffit pour la preuve UNSAT pure-SAT)
Theoreme de Sen non porte ce tranche SenSATEncoder Tranche 2
Benchmark solveurs 1 solveur (DPLL) 3 solveurs compares Tranche 2

Le coeur pedagogique - prouver Arrow par SAT solving - est couvert avec un solveur reel et complet. Les ecarts (Sen, benchmark multi-solveurs, scaling a 4+ alt) sont reportes en tranche 2 et documentes, pas maquilles.

5. Theoreme de Sen : un autre angle d’impossibilite

Le theoreme de Sen (1970) propose une impossibilite différente de celle d’Arrow, basee sur le conflit entre liberte individuelle minimale et efficacite collective. La ou Arrow exige IIA + non-dictature, Sen remplace ces deux axiomes par une seule contrainte de liberte minimale : chaque individu decide seul d’au moins une paire d’alternatives.

L’encodage SAT est donc plus parcimonieux (transitivite + Pareto + liberte), et UNSAT prouve que ces trois conditions sont déjà incompatibles – un résultat plus fort qu’Arrow car il ne requiert pas l’axiome IIA (souvent critique comme trop restrictif).

// === Encodeur SAT du theoreme de Sen (port C# de SenSATEncoder Python) ===
// Genere les clauses CNF pour : totalite, asymetrie, transitivite, Pareto, liberte minimale.
// Reutilise Permutations/Combinations definis au-dessus (cell Arrow).

public class SenSATEncoder
{
    public List<string> Alternatives { get; }
    public int NAlt => Alternatives.Count;
    public int NVoters { get; }
    public List<List<List<string>>> Profiles { get; }
    public List<(int voter, string x, string y)> LibertyPairs { get; }

    private readonly Dictionary<(int,string,string), int> _varMap = new();
    private int _varCounter = 0;

    public SenSATEncoder(List<string> alternatives, int nVoters, List<(int,string,string)> libertyPairs = null)
    {
        Alternatives = alternatives;
        NVoters = nVoters;
        // Paires de liberte par defaut : chaque voter decide d'une paire (round-robin)
        if (libertyPairs == null)
        {
            LibertyPairs = new List<(int,string,string)>();
            var allPairs = Combinations(alternatives, 2);
            for (int v = 0; v < nVoters; v++)
            {
                var pair = allPairs[v % allPairs.Count];
                LibertyPairs.Add((v, pair[0], pair[1]));
            }
        }
        else LibertyPairs = libertyPairs;

        var allOrders = Permutations(alternatives, NAlt).Select(p => p.ToList()).ToList();
        Profiles = new List<List<List<string>>>();
        void Build(int depth, List<List<string>> cur)
        {
            if (depth == nVoters) { Profiles.Add(cur.Select(o => o.ToList()).ToList()); return; }
            foreach (var order in allOrders) { cur.Add(order); Build(depth + 1, cur); cur.RemoveAt(cur.Count - 1); }
        }
        Build(0, new List<List<string>>());
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                _varMap[(pi, x, y)] = ++_varCounter;
    }

    public int GetVar(int pi, string x, string y) => _varMap[(pi, x, y)];

    public List<List<int>> EncodeCompleteness()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var pair in Combinations(Alternatives, 2))
                clauses.Add(new(){ GetVar(pi, pair[0], pair[1]), GetVar(pi, pair[1], pair[0]) });
        return clauses;
    }

    public List<List<int>> EncodeAsymmetry()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var pair in Combinations(Alternatives, 2))
                clauses.Add(new(){ -GetVar(pi, pair[0], pair[1]), -GetVar(pi, pair[1], pair[0]) });
        return clauses;
    }

    public List<List<int>> EncodeTransitivity()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var t in Permutations(Alternatives, 3).Select(p => p.ToList()))
                clauses.Add(new(){ -GetVar(pi, t[0], t[1]), -GetVar(pi, t[1], t[2]), GetVar(pi, t[0], t[2]) });
        return clauses;
    }

    public List<List<int>> EncodePareto()
    {
        var clauses = new List<List<int>>();
        for (int pi = 0; pi < Profiles.Count; pi++)
        {
            var profile = Profiles[pi];
            foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                if (profile.All(v => v.IndexOf(x) < v.IndexOf(y)))
                    clauses.Add(new(){ GetVar(pi, x, y) });
        }
        return clauses;
    }

    public List<List<int>> EncodeLiberty()
    {
        // Liberte minimale : pour chaque paire assignee a un voter, son choix est contraignant
        var clauses = new List<List<int>>();
        foreach (var (voter, x, y) in LibertyPairs)
            for (int pi = 0; pi < Profiles.Count; pi++)
            {
                if (Profiles[pi][voter].IndexOf(x) < Profiles[pi][voter].IndexOf(y))
                    clauses.Add(new(){ GetVar(pi, x, y) });
                else
                    clauses.Add(new(){ GetVar(pi, y, x) });
            }
        return clauses;
    }

    public List<List<int>> EncodeAll()
    {
        var all = new List<List<int>>();
        all.AddRange(EncodeCompleteness());
        all.AddRange(EncodeAsymmetry());
        all.AddRange(EncodeTransitivity());
        all.AddRange(EncodePareto());
        all.AddRange(EncodeLiberty());
        return all;
    }

    public int NVariables => _varCounter;
}

// Encodage de Sen : 3 alternatives, 2 votants
// Liberte : voter 0 decide (A,C), voter 1 decide (B,C)
var liberty = new List<(int,string,string)>{ (0,"A","C"), (1,"B","C") };
var senEnc = new SenSATEncoder(new List<string>{"A","B","C"}, 2, liberty);
var senClauses = senEnc.EncodeAll();

Console.WriteLine("ENCODAGE DU THEOREME DE SEN (3 alt, 2 voters)");
Console.WriteLine($"  Variables       : {senEnc.NVariables}");
Console.WriteLine($"  Clauses         : {senClauses.Count}");
Console.WriteLine($"  Pairs de liberte: voter0->(A,C), voter1->(B,C)");
Console.WriteLine();

var senWatch = System.Diagnostics.Stopwatch.StartNew();
var (senSat, senModel) = DpllSolver.Solve(senClauses, senEnc.NVariables);
senWatch.Stop();
Console.WriteLine($"Resultat DPLL    : {(senSat ? "SAT" : "UNSAT (Sen verifie)")} en {senWatch.Elapsed.TotalMilliseconds:F1} ms");
if (!senSat)
    Console.WriteLine("=> Liberte minimale + Pareto + transitivite = IMPOSSIBLE (theoreme de Sen)");
ENCODAGE DU THEOREME DE SEN (3 alt, 2 voters)
  Variables       : 216
  Clauses         : 558
  Pairs de liberte: voter0->(A,C), voter1->(B,C)

Resultat DPLL    : UNSAT (Sen verifie) en 0.1 ms
=> Liberte minimale + Pareto + transitivite = IMPOSSIBLE (theoreme de Sen)

Interpretation du theoreme de Sen. L’encodage produit moins de clauses que celui d’Arrow (transitivite + Pareto + liberte remplacent IIA + non-dictature qui sont quadratiques en profils). Le résultat UNSAT confirme le theoreme : on ne peut pas avoir simultanement le respect des droits individuels minimaux, l’efficacite collective (Pareto) et la coherence du classement social (transitivite).

Pourquoi Sen est “plus fort” qu’Arrow. Le theoreme de Sen ne requiert pas l’axiome IIA (Indépendance of Irrelevant Alternatives), souvent critique comme trop restrictif. Le conflit entre liberte et coherence collective est donc plus fondamental que le conflit entre coherence et non-dictature mis en lumiere par Arrow. Donner des droits individuels même minimaux mene a l’incoherence collective.


6. Benchmark structurel : Arrow vs Sen

La ou le notebook Python compare plusieurs solveurs CDCL (Glucose3, MiniSat22, CaDiCaL), le twin C# dispose d’un seul solveur (DPLL from-scratch, Prong B #3801). Le benchmark pertinent ici est structurel : comparer la taille de l’encodage Arrow vs Sen sur la même instance (3 alternatives, 2 votants) pour comprendre pourquoi Sen est computationnellement plus parcimonieux.

// === Benchmark structurel Arrow vs Sen (3 alt, 2 voters) ===
Console.WriteLine("BENCHMARK STRUCTUREL : ARROW vs SEN (3 alt, 2 voters)");
Console.WriteLine("=".PadRight(58, '='));
Console.WriteLine();

// Arrow
var arrEnc = new ArrowSATEncoder(new List<string>{"A","B","C"}, 2);
var arrClauses = arrEnc.EncodeAll();
var arrStats = arrEnc.Stats();
var arrWatch = System.Diagnostics.Stopwatch.StartNew();
var (arrSat, _) = DpllSolver.Solve(arrClauses, arrStats["variables"]);
arrWatch.Stop();

// Sen
var senBenc = new SenSATEncoder(new List<string>{"A","B","C"}, 2, liberty);
var senBclauses = senBenc.EncodeAll();
var senBwatch = System.Diagnostics.Stopwatch.StartNew();
var (senBsat, _) = DpllSolver.Solve(senBclauses, senBenc.NVariables);
senBwatch.Stop();

Console.WriteLine($"{"Theoreme",12} {"Profils",10} {"Vars",8} {"Clauses",10} {"Verdict",18} {"DPLL(ms)",10}");
Console.WriteLine("-".PadRight(58, '-'));
Console.WriteLine($"{"Arrow",12} {arrStats["profiles"],10} {arrStats["variables"],8} {arrClauses.Count,10} {(arrSat?"SAT":"UNSAT (Arrow)"),18} {arrWatch.Elapsed.TotalMilliseconds,10:F1}");
Console.WriteLine($"{"Sen",12} {arrStats["profiles"],10} {senBenc.NVariables,8} {senBclauses.Count,10} {(senBsat?"SAT":"UNSAT (Sen)"),18} {senBwatch.Elapsed.TotalMilliseconds,10:F1}");
Console.WriteLine();
Console.WriteLine($"Ratio clauses Arrow/Sen : {(double)arrClauses.Count/senBclauses.Count:F1}x");
Console.WriteLine("=> Sen remplace IIA (quadratique en profils) + non-dictature par une seule");
Console.WriteLine("   contrainte de liberte lineaire => encodage beaucoup plus compact.");
BENCHMARK STRUCTUREL : ARROW vs SEN (3 alt, 2 voters)
==========================================================

    Theoreme    Profils     Vars    Clauses            Verdict   DPLL(ms)
----------------------------------------------------------
       Arrow         36      216       2216      UNSAT (Arrow)        1.4
         Sen         36      216        558        UNSAT (Sen)        0.1

Ratio clauses Arrow/Sen : 4.0x
=> Sen remplace IIA (quadratique en profils) + non-dictature par une seule
   contrainte de liberte lineaire => encodage beaucoup plus compact.

Analyse du benchmark structurel.

Aspect Arrow Sen
Axiomes totalite + asymetrie + transitivite + Pareto + IIA + non-dictature totalite + asymetrie + transitivite + Pareto + liberte minimale
Source de la complexite IIA est quadratique en nombre de profils (compare toutes paires de profils) liberte est lineaire (une clause par paire assignee par profil)
Verdict UNSAT UNSAT

L’encodage de Sen est typiquement 3-4x plus compact en clauses que celui d’Arrow sur la même instance, car l’axiome IIA – qui compare chaque paire de profils partageant le même ordre sur une paire d’alternatives – genere un nombre de clauses quadratique en le nombre de profils. La liberte minimale, elle, n’ajoute qu’une clause unitaire par paire de liberte par profil.

Limite du DPLL from-scratch (Prong B #3801, verdict honnete). Sans apprentissage de clauses (clause-learning CDCL), DPLL explore un arbre de branchement exponentiel. Sur ces instances modestes (216 variables), le UNSAT est trouve en temps raisonnable, mais le passage a l’echelle (4+ alternatives ou 3+ votants) devient prohibitif. Les solveurs CDCL industriels (Glucose, CaDiCaL) gerent ces tailles grace a l’apprentissage de clauses et aux heuristiques VSIDS. La couverture SMT via Microsoft.Z3 (NuGet) est l’extension naturelle pour les instances plus larges (cf. Python original Partie 2).


7. Theoreme d’Arrow par SMT : Z3 et variables entieres

La partie précédente encode Arrow en booléens (SAT, x>y vrai/faux) et le resout avec DPLL. Le notebook Python original propose une deuxieme approche : l’arithmetique d’entiers via Z3 (SMT, théorie des entiers lineaires). Au lieu d’une variable booleenne par paire, on introduit un rang entier r_{pi}_{alt} in [0, n_alt) pour chaque alternative dans le classement social de chaque profil.

L’avantage du SMT : les contraintes d’ordre total (transitivite, antisymetrie) decoulent naturellement de la théorie des entiers, sans avoir a les enumerer en clauses CNF. Z3 resout le système par son solveur SMT, plus puissant que DPLL sur les gros encodages.

Verdict SOTA (EPIC #3801) : RECOVERABLE-LOCAL. Microsoft.Z3 4.12.2 (version max publiee sur NuGet) est installable et invocable en kernel .net-csharp (confirme cycle précédent : x>0 AND x<0 -> UNSATISFIABLE, x>5 -> SATISFIABLE x=6). On execute donc le vrai solveur Z3, pas un workaround.

Sous Linux et sous macOS Apple Silicon, le package NuGet ne contient pas la bibliothèque native de Z3 : la cellule suivante charge alors celle du paquet Python z3-solver, celui du twin Python, par Z3NativeLoader.cs de la série Z3-API (voir l’en-tête du fichier). Pour utiliser la même version de Z3 que le package NuGet : pip install z3-solver==4.12.2.0.

#r "nuget: Microsoft.Z3, 4.12.2"
#load "../../SymbolicAI/SMT/Z3-API/Z3NativeLoader.cs"
using Microsoft.Z3;

// Bibliotheque native de Z3 hors Windows et macOS Intel (voir Z3NativeLoader.cs)
Z3NativeLoader.Register(typeof(Context).Assembly);

// === Encodeur SMT du theoreme d'Arrow avec Z3 (port C# de ArrowZ3Encoder Python, Partie 2) ===
// Variables entieres : r_{pi}_{alt} = rang de alt dans le classement social du profil pi.
// Contraintes : ordre total (rangs dans [0,n_alt), antisymetrie), Pareto faible, IIA, non-dictature.

public class ArrowZ3Encoder
{
    public List<string> Alternatives { get; }
    public int NAlt => Alternatives.Count;
    public int NVoters { get; }
    public List<List<List<string>>> Profiles { get; }
    public Context Ctx { get; }
    public Solver Solver { get; }
    public int NConstraints => Solver.Assertions.Length;

    private readonly Dictionary<(int, string), IntExpr> _ranks;

    public ArrowZ3Encoder(Context ctx, List<string> alternatives, int nVoters)
    {
        Ctx = ctx;
        Alternatives = alternatives;
        NVoters = nVoters;
        // Generation exhaustive des profils (produit cartesien des ordres stricts possibles)
        var allOrders = Permutations(alternatives, NAlt).Select(p => p.ToList()).ToList();
        Profiles = new List<List<List<string>>>();
        void Build(int depth, List<List<string>> cur)
        {
            if (depth == nVoters) { Profiles.Add(cur.Select(o => o.ToList()).ToList()); return; }
            foreach (var o in allOrders) { cur.Add(o); Build(depth + 1, cur); cur.RemoveAt(cur.Count - 1); }
        }
        Build(0, new List<List<string>>());
        // Une variable entiere par (profil, alternative)
        _ranks = new Dictionary<(int, string), IntExpr>();
        for (int pi = 0; pi < Profiles.Count; pi++)
            foreach (var alt in alternatives)
                _ranks[(pi, alt)] = ctx.MkIntConst($"r_{pi}_{alt}");
        Solver = ctx.MkSolver();
        AddOrderConstraints();
    }

    private void AddOrderConstraints()
    {
        for (int pi = 0; pi < Profiles.Count; pi++)
        {
            foreach (var alt in Alternatives)
            {
                Solver.Assert(Ctx.MkGe(_ranks[(pi, alt)], Ctx.MkInt(0)));
                Solver.Assert(Ctx.MkLt(_ranks[(pi, alt)], Ctx.MkInt(NAlt)));
            }
            foreach (var pair in Combinations(Alternatives, 2))
                Solver.Assert(Ctx.MkNot(Ctx.MkEq(_ranks[(pi, pair[0])], _ranks[(pi, pair[1])])));
        }
    }

    private BoolExpr OrderRank(int pi, string x, string y) => Ctx.MkLt(_ranks[(pi, x)], _ranks[(pi, y)]);

    private static bool IndivPrefers(List<List<string>> profile, int voter, string x, string y)
        => profile[voter].IndexOf(x) < profile[voter].IndexOf(y);

    public void AddWeakPareto(System.Threading.CancellationToken ct = default)
    {
        for (int pi = 0; pi < Profiles.Count; pi++)
        {
            ct.ThrowIfCancellationRequested();
            var prof = Profiles[pi];
            foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                if (Enumerable.Range(0, NVoters).All(v => IndivPrefers(prof, v, x, y)))
                    Solver.Assert(OrderRank(pi, x, y));
        }
    }

    public void AddIIA(System.Threading.CancellationToken ct = default)
    {
        for (int pi1 = 0; pi1 < Profiles.Count; pi1++)
        {
            ct.ThrowIfCancellationRequested();
            for (int pi2 = pi1 + 1; pi2 < Profiles.Count; pi2++)
            {
                var p1 = Profiles[pi1]; var p2 = Profiles[pi2];
                foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                {
                    bool same = Enumerable.Range(0, NVoters).All(v => IndivPrefers(p1, v, x, y) == IndivPrefers(p2, v, x, y));
                    if (same)
                        Solver.Assert(Ctx.MkEq(OrderRank(pi1, x, y), OrderRank(pi2, x, y)));
                }
            }
        }
    }

    public void AddNoDictator(System.Threading.CancellationToken ct = default)
    {
        for (int voter = 0; voter < NVoters; voter++)
        {
            ct.ThrowIfCancellationRequested();
            var violations = new List<BoolExpr>();
            for (int pi = 0; pi < Profiles.Count; pi++)
            {
                var prof = Profiles[pi];
                foreach (var (x, y) in from a in Alternatives from b in Alternatives where a != b select (a, b))
                    if (IndivPrefers(prof, voter, x, y))
                        violations.Add(Ctx.MkNot(OrderRank(pi, x, y)));
            }
            if (violations.Count > 0)
                Solver.Assert(Ctx.MkOr(violations.ToArray()));
        }
    }
}

// === Verification du theoreme d'Arrow par Z3 SMT (3 alternatives, 2 electeurs) ===
var z3Watch = System.Diagnostics.Stopwatch.StartNew();
var z3Ctx = new Context();
var arrZ3 = new ArrowZ3Encoder(z3Ctx, new List<string>{"A","B","C"}, 2);
arrZ3.AddWeakPareto();
arrZ3.AddIIA();
arrZ3.AddNoDictator();
var z3Status = arrZ3.Solver.Check();
z3Watch.Stop();

Console.WriteLine("VERIFICATION DU THEOREME D'ARROW PAR z3 (SMT)");
Console.WriteLine($"  Configuration       : {arrZ3.NAlt} alternatives, {arrZ3.NVoters} electeurs");
Console.WriteLine($"  Profils             : {arrZ3.Profiles.Count}");
Console.WriteLine($"  Variables entieres  : {arrZ3.NAlt * arrZ3.Profiles.Count}");
Console.WriteLine($"  Contraintes (assert): {arrZ3.NConstraints}");
Console.WriteLine($"  Resultat            : {z3Status.ToString().ToLower()}");
Console.WriteLine($"  Temps               : {z3Watch.Elapsed.TotalMilliseconds:F1} ms");
if (z3Status == Status.UNSATISFIABLE)
    Console.WriteLine("=> Arrow verifie : Pareto + IIA + non-dictature = IMPOSSIBLE (SMT entiers)");
Installed Packages
  • Microsoft.Z3, 4.12.2
VERIFICATION DU THEOREME D'ARROW PAR z3 (SMT)
  Configuration       : 3 alternatives, 2 electeurs
  Profils             : 36
  Variables entieres  : 108
  Contraintes (assert): 1244
  Resultat            : unsatisfiable
  Temps               : 55.9 ms
=> Arrow verifie : Pareto + IIA + non-dictature = IMPOSSIBLE (SMT entiers)
// === Relaxation : 2 alternatives = SAT, et benchmark SAT vs SMT ===
// Avec 2 alternatives, aucun cycle n'est possible : le theoreme d'Arrow ne tient plus.
var z3Ctx2 = new Context();
var arr2 = new ArrowZ3Encoder(z3Ctx2, new List<string>{"A","B"}, 2);
arr2.AddWeakPareto(); arr2.AddIIA(); arr2.AddNoDictator();
var z3s2 = arr2.Solver.Check();

Console.WriteLine("RELAXATION 2 ALTERNATIVES (SMT)");
Console.WriteLine($"  Profils : {arr2.Profiles.Count}, vars entieres : {arr2.NAlt * arr2.Profiles.Count}");
Console.WriteLine($"  Resultat : {z3s2.ToString().ToLower()}");

Console.WriteLine();
Console.WriteLine("BENCHMARK ARROW : SAT (DPLL, booleens) vs SMT (Z3, entiers)");
Console.WriteLine("=".PadRight(62, '='));
// Re-encode Arrow SAT pour comparer (DPLL, tranche 1/2)
var arrEncS = new ArrowSATEncoder(new List<string>{"A","B","C"}, 2);
var arrClausesS = arrEncS.EncodeAll();
var arrStatsS = arrEncS.Stats();
var swSat = System.Diagnostics.Stopwatch.StartNew();
var (sat3, _) = DpllSolver.Solve(arrClausesS, arrStatsS["variables"]);
swSat.Stop();
Console.WriteLine($"{"Methode",20} {"Variables",12} {"Contraintes",14} {"Verdict",10} {"Temps(ms)",10}");
Console.WriteLine("-".PadRight(62, '-'));
Console.WriteLine($"{"SAT (DPLL booleens)",20} {arrStatsS["variables"],12} {arrClausesS.Count,14} {(sat3?"SAT":"UNSAT"),10} {swSat.Elapsed.TotalMilliseconds,10:F1}");
Console.WriteLine($"{"SMT (Z3 entiers)",20} {arrZ3.NAlt * arrZ3.Profiles.Count,12} {arrZ3.NConstraints,14} {(z3Status==Status.UNSATISFIABLE?"UNSAT":"SAT"),10} {z3Watch.Elapsed.TotalMilliseconds,10:F1}");
Console.WriteLine();
Console.WriteLine("=> Les deux methodes convergent : UNSAT. SAT encode l'ordre en booleens");
Console.WriteLine("   (une clause par paire), SMT en rangs entiers (theorie arithmetique native).");
RELAXATION 2 ALTERNATIVES (SMT)
  Profils : 4, vars entieres : 8
  Resultat : satisfiable

BENCHMARK ARROW : SAT (DPLL, booleens) vs SMT (Z3, entiers)
==============================================================
             Methode    Variables    Contraintes    Verdict  Temps(ms)
--------------------------------------------------------------
 SAT (DPLL booleens)          216           2216      UNSAT        1.6
    SMT (Z3 entiers)          108           1244      UNSAT       55.9

=> Les deux methodes convergent : UNSAT. SAT encode l'ordre en booleens
   (une clause par paire), SMT en rangs entiers (theorie arithmetique native).

Interpretation du résultat SMT. Z3 confirme le même verdict que DPLL : UNSAT. Les deux approches prouvent le theoreme d’Arrow, mais par des voies radicalement différentes :

Aspect SAT (DPLL, booléens) SMT (Z3, entiers)
Encodage une variable booleenne par paire x>y un rang entier r_{pi}_{alt} in [0,n_alt) par alternative
Transitivite clauses CNF explicites (quadratique) decoule de la théorie des entiers lineaires (native)
Solveur DPLL from-scratch (branchement exponentiel) Z3 (CDCL + théorie arithmetique)
Passage a l’echelle prohibitif au-dela de 3 alt / 2 votants supporte des instances plus larges

Pourquoi le SMT est l’extension naturelle pour les grandes instances. L’encodage booléen d’Arrow explose en clauses a mesure que le nombre d’alternatives ou de votants augmente (IIA est quadratique en profils). Le SMT delegue le raisonnement arithmetique au solveur Z3, qui dispose d’apprentissage de clauses (CDCL) et d’une decision guidee par la théorie. C’est pourquoi le notebook Python original conclut sur Z3 pour les instances au-dela du cas jouet.

Limite honnete (règle G.2). Sur cette instance jouet (3 alternatives, 2 votants), DPLL from-scratch (Prong B #3801) résout quasi-instantanément (temps mesuré dynamiquement par la cellule de benchmark ci-dessus) tandis que Z3, malgre sa puissance, met plus de temps (initialisation du Context + resolution). Z3 n’est avantageux que sur des instances plus larges ; sur le cas jouet, DPLL est suffisant et plus leger (0 dépendance NuGet). Les deux outils cohabitent ici a but pedagogique : comparer deux paradigmes de resolution du même theoreme d’impossibilite.

7.1 Relaxation des axiomes : chaque paire est réalisable

Le théorème d’Arrow dit que les trois axiomes réunis (Pareto faible + IIA + non-dictature) sont incompatibles dès 3 alternatives. Que se passe-t-il si l’on n’en garde que deux ? Le notebook Python (partie 2, « Relaxation des axiomes ») teste les trois paires possibles. Miroir C# : trois encodeurs Z3 frais (3 alternatives, 2 électeurs), chacun relâche un axiome différent.

Cas Axiomes gardés Témoin attendu
1 Pareto + IIA une dictature
2 Pareto + non-dictature une règle de position (Borda)
3 IIA + non-dictature une SWF triviale
// === Relaxation : chaque PAIRE d'axiomes est realisable (3 alternatives, 2 electeurs) ===
// Miroir de la section Python "Relaxation des axiomes" : l'impossibilite n'apparait
// qu'avec les TROIS axiomes reunis (cellule precedente : UNSAT).
var alt3R = new List<string> {"A", "B", "C"};

Console.WriteLine("RELAXATION DES AXIOMES D'ARROW (SMT, 3 alternatives, 2 electeurs)");
Console.WriteLine("=".PadRight(64, '='));

// Cas 1 : Pareto + IIA (sans non-dictature) -> une dictature satisfait les deux
var ctxR1 = new Context();
var encR1 = new ArrowZ3Encoder(ctxR1, alt3R, 2);
encR1.AddWeakPareto(); encR1.AddIIA();
var stR1 = encR1.Solver.Check();
Console.WriteLine($"  Cas 1 : Pareto + IIA            -> {stR1.ToString().ToLower(),14} (temoin : dictature)");

// Cas 2 : Pareto + non-dictateur (sans IIA) -> une regle de Borda satisfait les deux
var ctxR2 = new Context();
var encR2 = new ArrowZ3Encoder(ctxR2, alt3R, 2);
encR2.AddWeakPareto(); encR2.AddNoDictator();
var stR2 = encR2.Solver.Check();
Console.WriteLine($"  Cas 2 : Pareto + non-dictature  -> {stR2.ToString().ToLower(),14} (temoin : Borda)");

// Cas 3 : IIA + non-dictateur (sans Pareto) -> une SWF triviale satisfait les deux
var ctxR3 = new Context();
var encR3 = new ArrowZ3Encoder(ctxR3, alt3R, 2);
encR3.AddIIA(); encR3.AddNoDictator();
var stR3 = encR3.Solver.Check();
Console.WriteLine($"  Cas 3 : IIA + non-dictature     -> {stR3.ToString().ToLower(),14} (temoin : SWF triviale)");

Console.WriteLine();
Console.WriteLine("=> Chaque paire d'axiomes est realisable : l'impossibilite n'apparait");
Console.WriteLine("   qu'en reunissant les TROIS axiomes (cas complet : UNSAT).");
RELAXATION DES AXIOMES D'ARROW (SMT, 3 alternatives, 2 electeurs)
================================================================
  Cas 1 : Pareto + IIA            ->    satisfiable (temoin : dictature)
  Cas 2 : Pareto + non-dictature  ->    satisfiable (temoin : Borda)
  Cas 3 : IIA + non-dictature     ->    satisfiable (temoin : SWF triviale)

=> Chaque paire d'axiomes est realisable : l'impossibilite n'apparait
   qu'en reunissant les TROIS axiomes (cas complet : UNSAT).

Lecture du résultat. Les trois paires sont satisfiable : la frontière de l’impossibilité est exactement le trio d’axiomes, pas une paire. Chaque relaxation ouvre une porte connue — une dictature (Pareto+IIA), une règle de position type Borda (Pareto+non-dictature), une fonction triviale (IIA+non-dictature). C’est la lecture constructive du théorème : abandonner un seul axiome suffit à rétablir l’existence d’une fonction de choix social.

7.2 Passage à l’échelle : grille alternatives × électeurs

Miroir du benchmark Python : grille 2-3 alternatives × 2-3 électeurs, chaque case ré-encode Arrow complet (trois axiomes) et mesure Z3. La frontière attendue est structurelle : 2 alternatives = SAT (aucun cycle possible), 3 alternatives = UNSAT (le théorème s’applique).

// === Benchmark : passage a l'echelle (grille alt x voters, miroir Python) ===
Console.WriteLine("PASSAGE A L'ECHELLE : ARROW SMT (Z3)");
Console.WriteLine("=".PadRight(70, '='));
Console.WriteLine($"{"Alt",4} {"Voters",7} {"Profils",9} {"Vars int",9} {"Resultat",10} {"Temps(ms)",10}");
Console.WriteLine("-".PadRight(70, '-'));
foreach (var nAltG in new[] {2, 3})
{
    foreach (var nVG in new[] {2, 3})
    {
        var altsG = Enumerable.Range(0, nAltG).Select(i => ((char)('A' + i)).ToString()).ToList();
        var swG = System.Diagnostics.Stopwatch.StartNew();
        var ctxG = new Context();
        var encG = new ArrowZ3Encoder(ctxG, altsG, nVG);
        encG.AddWeakPareto(); encG.AddIIA(); encG.AddNoDictator();
        var stG = encG.Solver.Check();
        swG.Stop();
        var verdictG = stG == Status.UNSATISFIABLE ? "UNSAT" : "SAT";
        Console.WriteLine($"{nAltG,4} {nVG,7} {encG.Profiles.Count,9} {encG.NAlt * encG.Profiles.Count,9} {verdictG,10} {swG.Elapsed.TotalMilliseconds,10:F1}");
    }
}
Console.WriteLine();
// 4 alternatives : mesure sous budget (miroir Python #13929). Le mur est la
// construction (AddIIA est O(profils^2)), pas le check : un CancellationToken
// borne la construction -- equivalent C# du signal.alarm du twin Python.
foreach (var (nAlt4, nV4, budget4) in new[] {(4, 2, 90), (4, 3, 15)})
{
    var alts4 = Enumerable.Range(0, nAlt4).Select(i => ((char)('A' + i)).ToString()).ToList();
    var sw4 = System.Diagnostics.Stopwatch.StartNew();
    using (var ctx4 = new Context())
    using (var cts4 = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(budget4)))
    {
        var enc4 = new ArrowZ3Encoder(ctx4, alts4, nV4);
        try
        {
            enc4.AddWeakPareto(cts4.Token); enc4.AddIIA(cts4.Token); enc4.AddNoDictator(cts4.Token);
            var st4 = enc4.Solver.Check();
            sw4.Stop();
            var verdict4 = st4 == Status.UNSATISFIABLE ? "UNSAT" : "SAT";
            Console.WriteLine($"{nAlt4,4} {nV4,7} {enc4.Profiles.Count,9} {enc4.NAlt * enc4.Profiles.Count,9} {verdict4,10} {sw4.Elapsed.TotalMilliseconds,10:F1}");
        }
        catch (OperationCanceledException)
        {
            sw4.Stop();
            Console.WriteLine($"{nAlt4,4} {nV4,7} {enc4.Profiles.Count,9} {enc4.NAlt * enc4.Profiles.Count,9} {"ABANDON",10}  construction > budget {budget4} s ({sw4.Elapsed.TotalMilliseconds,6:F0} ms ecoules)");
        }
    }
}
Console.WriteLine();
Console.WriteLine("FRONTIERE MESUREE : 4 alternatives x 2 electeurs tiennent (UNSAT) ; c'est la profondeur du profil (3 electeurs = 13 824 profils), pas la largeur, qui fait exploser la construction.");
PASSAGE A L'ECHELLE : ARROW SMT (Z3)
======================================================================
 Alt  Voters   Profils  Vars int   Resultat  Temps(ms)
----------------------------------------------------------------------
   2       2         4         8        SAT      144.3
   2       3         8        16        SAT      106.8
   3       2        36       108      UNSAT       94.4
   3       3       216       648      UNSAT      500.9

   4       2       576      2304      UNSAT     7989.1
   4       3     13824     55296    ABANDON  construction > budget 15 s ( 15092 ms ecoules)

FRONTIERE MESUREE : 4 alternatives x 2 electeurs tiennent (UNSAT) ; c'est la profondeur du profil (3 electeurs = 13 824 profils), pas la largeur, qui fait exploser la construction.

Lecture du résultat. La grille exhibe la frontière exacte du théorème : les deux lignes à 2 alternatives sont SAT (aucun cycle possible — le théorème exige |A| >= 3, cf. la relaxation de la section précédente), les deux à 3 alternatives sont UNSAT. Côté coût, le temps Z3 croît fortement avec la grille au rythme de l’explosion du nombre de profils ; la case (3, 3) reste largement jouable — le jumeau Python mesure un ordre de grandeur ~1 s sur la même case. Point honnête : à cette échelle notebook, le DPLL booléen from-scratch de la partie 1 résout le même cas Arrow (3 alternatives, 2 électeurs) plus vite que Z3 sur le même cas : le backtracking compilé n’a pas de surcoût constant, et l’avantage d’un solveur de production (apprentissage de clauses, heuristiques de branchement) ne devient visible qu’au-delà de ces tailles. Les deux encodages — booléen (partie 1) et rangs entiers (partie 2, cette section) — convergent sur les mêmes verdicts. L’estimation à 4 alternatives marque la limite pratique : 576 profils (2 électeurs) reste jouable, 13 824 profils (3 électeurs) sort du cadre du notebook.

Note (mandat #9377/#9434) : les mesures de temps Z3 et DPLL visibles dans la grille §7.2 sont des mesures runtime machine-dépendantes (charge CPU du runner + GC .NET + JIT warmup) drainées de cette prose : les tendances qualitatives (Z3 croît fortement avec la grille, DPLL from-scratch bat Z3 à petite échelle sur la même instance) sont reproductibles et conservées. Les mesures exactes restent visibles dans les cellules de code amont (la grille §7.2 mesure ces 4 cas via sw.ElapsedMilliseconds dans la cellule de banc d’essai Z3/DPLL — c’est leur place légitime, et elles ne sont pas scrubbées). Le Python twin (jumeau 04-Computational-Aggregation-SAT-Z3.ipynb) est non touché dans cette tranche (0 résidu détecté par measure_residual_machine_dep.py, déjà drainé upstream c.1054 par po-2023:CoursIA-2 EPIC #8052).

Extension 4 alternatives (frontiere mesuree, miroir #13929). La grille se poursuit sous budget : 4 alternatives x 2 electeurs (576 profils, 2 304 variables entieres) est UNSAT - la frontiere recule, le cas tient dans le notebook. A 4 alternatives x 3 electeurs (13 824 profils), la construction depasse le budget : abandon mesure, pas estimation. C’est la profondeur du profil, pas la largeur en alternatives, qui referme le mecanique.


8. Pont industriel : les solveurs PySAT via PythonNet

Le DPLL from-scratch des sections precedentes est sound et complet, mais sans clause learning il n’atteint pas les performances des solveurs industriels. Le twin Python (04-Computational-Aggregation-SAT-Z3.ipynb, cellule 11) benchmarque trois solveurs CDCL via PySAT. Cette section ouvre le meme acces depuis C# par un pont PythonNet (.NET -> Python.Runtime -> CPython -> pysat) : les solveurs industriels s’executent sur le meme encodage que celui produit par ArrowSATEncoder (Section 3).

Recette du pont (#10382, axe 5 PythonNet) : package pythonnet 3.1.0 (la 3.0.5 repose sur BinaryFormatter, supprime de .NET 9 ; les previews 3.1.0 anterieures a la sortie de CPython 3.13 ne chargent pas son ABI) + Runtime.PythonDLL pointant vers un CPython 3.13 dans lequel python-sat est installe (pip install python-sat). La variable d’environnement PYTHONNET_PYDLL surcharge le chemin par defaut. Sans cette variable ni ce fichier, la cellule interroge le premier interpréteur (python3, puis python) qui importe pysat, et en reprend la bibliothèque et le sys.path : sous Linux et macOS, pip install python-sat suffit, environnement virtuel compris.

Le pont est en plus, jamais a la place : le DPLL from-scratch reste l’outil pedagogique des sections 1 a 6 ; le pont fournit la jambe industrielle qui manquait au twin C# (registre twin_pairs.d, axe lib-vs-lib).

#r "nuget: pythonnet,3.1.0"
using Python.Runtime;

// === Pont industriel : PySAT via PythonNet (axe 5 #10382) ===
// Les 3 solveurs CDCL du twin Python (cellule 11) sur le MEME encodage C# que le
// DPLL from-scratch de la Section 3. Pont EN PLUS, jamais a la place.

// Repli portable (Linux, macOS, ou Windows sans le CPython ci-dessus) : le premier
// interpreteur (python3, puis python) qui importe le module fournit sa bibliotheque
// partagee, son prefixe et son sys.path, environnement virtuel compris.
static void InitPythonFromInterpreter(string module)
{
    const string probe =
        "import importlib, json, os, sys, sysconfig\n" +
        "importlib.import_module(sys.argv[1])\n" +
        "v = sysconfig.get_config_var; lib = v('LIBDIR') or ''; mm = sys.version_info[:2]\n" +
        "c = [os.path.join(lib, n) for n in (v('INSTSONAME'), v('LDLIBRARY')) if n]\n" +
        "c += [os.path.join(d, 'libpython%d.%d.dylib' % mm) for d in (lib, os.path.join(sys.base_prefix, 'lib'))]\n" +
        "c.append(os.path.join(sys.base_prefix, 'python%d%d.dll' % mm))\n" +
        "dll = next((p for p in c if os.path.isfile(p)), '')\n" +
        "print(json.dumps({'dll': dll, 'home': sys.base_prefix, 'path': [p for p in sys.path if p]}))\n";
    foreach (var exe in new[] { "python3", "python" })
    {
        try
        {
            var psi = new System.Diagnostics.ProcessStartInfo(exe)
            { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false };
            foreach (var arg in new[] { "-c", probe, module }) psi.ArgumentList.Add(arg);
            using var p = System.Diagnostics.Process.Start(psi);
            var stderr = p.StandardError.ReadToEndAsync();
            string json = p.StandardOutput.ReadToEnd();
            p.WaitForExit();
            if (p.ExitCode != 0) continue;
            var info = System.Text.Json.JsonDocument.Parse(json).RootElement;
            string dll = info.GetProperty("dll").GetString();
            if (string.IsNullOrEmpty(dll)) continue;
            Runtime.PythonDLL = dll;
            PythonEngine.PythonHome = info.GetProperty("home").GetString();
            PythonEngine.Initialize();
            using (Py.GIL())
            {
                var path = info.GetProperty("path").EnumerateArray()
                    .Select(e => (PyObject)new PyString(e.GetString())).ToArray();
                Py.Import("sys").SetAttr("path", new PyList(path));
            }
            return;
        }
        catch (System.ComponentModel.Win32Exception) { }   // interpreteur absent du PATH
    }
    throw new System.IO.FileNotFoundException(
        $"Aucun CPython n'importe {module} : l'installer (pip install), ou definir PYTHONNET_PYDLL.");
}

var pyDll = Environment.GetEnvironmentVariable("PYTHONNET_PYDLL")
    ?? @"C:\Users\jsboi\AppData\Local\Programs\Python\Python313\python313.dll";
if (System.IO.File.Exists(pyDll)) { Runtime.PythonDLL = pyDll; PythonEngine.Initialize(); }
else InitPythonFromInterpreter("pysat");
Console.WriteLine($"Pont PythonNet : CPython {PythonEngine.Version}, pythonnet 3.1.0");
Console.WriteLine();

// Serialisation des clauses C# (enc.EncodeAll(), Section 3) vers le scope Python
string clausesJson = "[" + string.Join(",", clausesArrow.Select(c => "[" + string.Join(",", c) + "]")) + "]";

Console.WriteLine("PYSAT SUR L'ENCODAGE D'ARROW (3 alternatives, 2 votants)");
Console.WriteLine("=".PadRight(54, '='));
Console.WriteLine($"Meme instance que DPLL : {statsEnc["variables"]} variables, {clausesArrow.Count} clauses");
Console.WriteLine();

using (Py.GIL())
{
    dynamic scope = Py.CreateScope();
    scope.Set("clauses_json", clausesJson);
    scope.Exec(@"
import json, time
from pysat.solvers import Glucose3, Minisat22, Cadical103

clauses = json.loads(clauses_json)
lines = []
for name, cls in [('Glucose3', Glucose3), ('MiniSat22', Minisat22), ('CaDiCaL103', Cadical103)]:
    t0 = time.perf_counter()
    with cls(bootstrap_with=clauses) as s:
        sat = s.solve()
    dt_ms = (time.perf_counter() - t0) * 1000.0
    verdict = 'SAT (contredit Arrow !)' if sat else 'UNSAT (Arrow verifie)'
    lines.append(f'{name:12s} : {verdict}  [{dt_ms:8.1f} ms]')
summary = chr(10).join(lines)");
    Console.WriteLine(scope.Get<string>("summary"));
    Console.WriteLine();
    Console.WriteLine("Les 3 solveurs CDCL retrouvent le verdict du DPLL (Section 3) et du twin Python (cellule 11).");
}

// === 4 alternatives, 2 votants : memes 3 solveurs CDCL (miroir du Python #13929) ===
var enc4sat = new ArrowSATEncoder(new List<string>{"A","B","C","D"}, nVoters: 2);
var sw4enc = System.Diagnostics.Stopwatch.StartNew();
var clauses4 = enc4sat.EncodeAll();
sw4enc.Stop();
var stats4 = enc4sat.Stats();
Console.WriteLine();
Console.WriteLine("PYSAT SUR L'ENCODAGE D'ARROW (4 alternatives, 2 votants)");
Console.WriteLine("=".PadRight(54, '='));
Console.WriteLine($"Encodage : {stats4["profiles"]} profils, {stats4["variables"]} variables, {stats4["clauses"]} clauses, construction {sw4enc.Elapsed.TotalMilliseconds:F0} ms");
string clauses4Json = "[" + string.Join(",", clauses4.Select(c => "[" + string.Join(",", c) + "]")) + "]";

using (Py.GIL())
{
    dynamic scope4 = Py.CreateScope();
    scope4.Set("clauses_json", clauses4Json);
    scope4.Exec(@"
import json, time
from pysat.solvers import Glucose3, Minisat22, Cadical103

clauses = json.loads(clauses_json)
lines = []
for name, cls in [('Glucose3', Glucose3), ('MiniSat22', Minisat22), ('CaDiCaL103', Cadical103)]:
    t0 = time.perf_counter()
    with cls(bootstrap_with=clauses) as s:
        sat = s.solve()
    dt_ms = (time.perf_counter() - t0) * 1000.0
    verdict = 'SAT (contredit Arrow !)' if sat else 'UNSAT (Arrow verifie)'
    lines.append(f'{name:12s} : {verdict}  [{dt_ms:8.1f} ms]')
summary = chr(10).join(lines)");
    Console.WriteLine(scope4.Get<string>("summary"));
}
PythonEngine.Shutdown();
Installed Packages
  • pythonnet, 3.1.0
Pont PythonNet : CPython 3.11.15 (main, Mar  3 2026, 09:26:23) [GCC 13.3.0], pythonnet 3.1.0

PYSAT SUR L'ENCODAGE D'ARROW (3 alternatives, 2 votants)
======================================================
Meme instance que DPLL : 216 variables, 2216 clauses

Glucose3     : UNSAT (Arrow verifie)  [     1.1 ms]
MiniSat22    : UNSAT (Arrow verifie)  [     0.8 ms]
CaDiCaL103   : UNSAT (Arrow verifie)  [     2.6 ms]

Les 3 solveurs CDCL retrouvent le verdict du DPLL (Section 3) et du twin Python (cellule 11).

PYSAT SUR L'ENCODAGE D'ARROW (4 alternatives, 2 votants)
======================================================
Encodage : 576 profils, 6912 variables, 1010882 clauses, construction 2240 ms
Glucose3     : UNSAT (Arrow verifie)  [   286.2 ms]
MiniSat22    : UNSAT (Arrow verifie)  [   290.4 ms]
CaDiCaL103   : UNSAT (Arrow verifie)  [   434.9 ms]

Lecture du résultat. Les trois solveurs CDCL (Glucose3, MiniSat22, CaDiCaL103) concluent UNSAT sur l’encodage d’Arrow produit par l’ArrowSATEncoder C# — le même verdict que le DPLL from-scratch de la Section 3 et que le twin Python (cellule 11), sur la même instance (216 variables, 2216 clauses). C’est la parité lib-vs-lib complète du registre #10382 : chaque twin atteint un moteur de production de son écosystème (DPLL + Z3 natifs côté .NET, PySAT côté Python), et le pont donne désormais aussi au twin C# l’accès direct aux solveurs industriels. Sur cette instance le DPLL from-scratch (Section 3) tranche lui aussi quasi instantanément — c’est sur le passage à l’échelle (grandes instances, grille 7.2) que le clause learning des solveurs industriels fait la différence, la limite honnêtement documentée depuis l’intro de ce notebook.

Extension 4 alternatives. Le pont enchaîne sur l’encodage 4 alt x 2 votants (576 profils, 6 912 variables, 1 010 882 clauses) : les trois solveurs CDCL concluent UNSAT, miroir de la cellule 11 du twin Python (~3 s).

9. Exercices

Exercice 1 : Purs litteraux

Un literal est pur s’il n’apparait qu’avec un signe dans toute la CNF. La règle des litteraux purs (pure literal elimination) l’assigne pour satisfaire toutes ses clauses. Ajoutez cette règle au solveur DPLL (avant le branchement) et mesurez l’acceleration sur l’encodage d’Arrow.

Exercice 2 : Robustesse de Sen au choix des paires de liberte

Le theoreme de Sen (Section 5) est prouve UNSAT avec une assignation spécifique des paires de liberte (voter 0 -> (A,C), voter 1 -> (B,C)). Testez la robustesse de l’axiome de liberte minimale : changez l’assignation des paires (ex. voter 0 -> (A,B), voter 1 -> (A,C)) et verifiez que l’encodage reste UNSAT avec DPLL. L’impossibilite de Sen depend-elle du choix des paires assignees, ou est-elle structurelle (valable pour toute assignation couvrante) ?

Exercice 3 : Sen par SMT (Z3, rangs entiers)

La Section 7 encode Arrow par SMT (Z3, rangs entiers r_{pi}_{alt}). Portez l’encodeur SMT de Sen (transitivite native des entiers + Pareto + liberte minimale) et verifiez UNSAT avec Z3. Comparez le nombre de contraintes SMT avec l’encodage SAT booléen de la Section 5. Le paradigme par rangs entiers est-il plus compact pour Sen aussi ?

// === Exercices (stubs) ===

// Exercice 1 : elimination des litteraux purs dans DpllSolver
// Indice : avant UnitPropagate, scanner tous les litteraux de la CNF ; si un literal
// n'apparait jamais avec le signe oppose, l'assigner pour satisfaire ses clauses.
// Chronometrer ensuite Solve sur l'encodage d'Arrow avant/apres et retourner le ratio.
public static double PureLiteralSpeedupPlaceholder()
{
    // TODO etudiant : ajouter la regle des litteraux purs a DpllSolver,
    // mesurer le ratio temps_avant / temps_apres sur Arrow (> 1.0 = acceleration).
    return 0.0;  // TODO etudiant
}

// Exercice 2 : robustesse de Sen au choix des paires de liberte
// Indice : SenSATEncoder (cell Section 5) prend une liste libertyPairs en constructeur.
// Tester plusieurs assignations de paires differentes (ex. voter0->(A,B), voter1->(A,C))
// et verifier que DpllSolver.Solve reste UNSAT pour chacune.
public static bool SenLibertyPairRobustnessPlaceholder()
{
    // TODO etudiant : tester >= 3 assignations de paires de liberte distinctes,
    // retourner true si toutes donnent UNSAT (Sen robuste au choix des paires).
    return false;  // TODO etudiant
}

// Exercice 3 : theoreme de Sen par SMT (Z3, rangs entiers)
// Indice : ArrowZ3Encoder (cell Section 7) encode Arrow en rangs entiers. Faire de meme
// pour Sen (transitivite native + Pareto + liberte minimale) avec un Context Z3,
// verifier UNSAT, et comparer le nombre d'assertions avec l'encodage SAT (Section 5).
public static (bool unsat, int nConstraints) SenZ3Placeholder()
{
    // TODO etudiant : construire un Context Z3, encoder Sen en rangs entiers,
    // retourner (solver.Check() == UNSATISFIABLE, solver.Assertions.Length).
    return (false, 0);  // TODO etudiant
}

Console.WriteLine("3 exercices definis (stubs) : purs litteraux (DPLL), robustesse Sen (liberty-pairs), Sen par Z3 (SMT).");
3 exercices definis (stubs) : purs litteraux (DPLL), robustesse Sen (liberty-pairs), Sen par Z3 (SMT).

Conclusion

Ce que vous avez appris

  • SAT comme outil de preuve. Un theoreme d’impossibilite se prouve en encodant ses axiomes en CNF et en montrant que la formule est UNSAT : aucun objet ne satisfait les axiomes.
  • DPLL, l’algorithme canonique. Unit propagation + branchement + backtracking. Sound et complet. La base dont derivent les solveurs industriels (CDCL, clause learning).
  • Arrow mecaniquement. L’encodage (216 variables, 2216 clauses pour 3 alternatives) confirme UNSAT : aucune SWF ne satisfait Pareto + IIA + non-dictature. Le cas 2 alternatives retourne SAT (une SWF non dictatoriale existe) : c’est la frontiere exacte du theoreme, qui suppose \(|A| \geq 3\).

Limite honnete (Prong B)

DPLL from-scratch sans clause-learning scale mal au-dela de 3-4 alternatives. Le notebook Python (PySAT/Glucose3) va plus loin grace a CDCL. Ce twin privilegie la transparence pedagogique (solveur lisible, 0 NuGet, coherent avec 01-Arrow-Csharp et 03-Voting-Methods-Csharp) et documente ce plafond au lieu de le maquiller.

Prochaines étapes

  1. Litteraux purs (exercice 1) : accelerer DPLL par l’elimination des litteraux purs.
  2. Robustesse de Sen (exercice 2) : tester la sensibilite au choix des paires de liberte.
  3. Sen par SMT (exercice 3) : encoder Sen avec Z3 et comparer avec l’encodage SAT.
  4. Comparer avec le twin Python : 04-Computational-Aggregation-SAT-Z3.ipynb pour la version PySAT (3 solveurs industriels) et l’encodage de Sen.

References

  • Arrow, K. J. (1951). Social Choice and Individual Values. Wiley.
  • Sen, A. K. (1970). The Impossibility of a Paretian Liberal. Journal of Political Economy.
  • Davis, M., Logemann, G., Loveland, D. (1962). A machine program for theorem-proving. CACM.
  • Documentation QuantConnect (serie parente)
Retour au sommet