// Hierarchical CSP : Configuration PC — MANDATORY > STRONG > WEAK
// Miroir de l'exemple resolu du jumeau Python (OR-Tools CP-SAT) :
// meme catalogue, meme M = 5 — l'accord des deux moteurs sur l'optimum fait foi.
var model6 = new Model("HierarchicalCSP — Configuration PC");
// ---- Catalogue (identique au jumeau Python) ----
// socket : 0 = LGA1700 (Intel), 1 = AM4 (AMD) ; brand : 0 = AMD, 1 = Intel/Nvidia
string[] cpu6Names = { "Intel Core i5-12400", "Intel Core i7-12700", "AMD Ryzen 5 5600", "AMD Ryzen 7 5800X" };
int[] cpu6Socket = { 0, 0, 1, 1 }; int[] cpu6Tdp = { 65, 125, 65, 105 };
int[] cpu6Score = { 70, 90, 68, 88 }; int[] cpu6Price = { 200, 350, 180, 300 }; int[] cpu6Brand = { 1, 1, 0, 0 };
string[] mb6Names = { "MSI PRO B660M", "Asus ROG Z690", "Gigabyte B550M", "Asus TUF B550" };
int[] mb6Socket = { 0, 0, 1, 1 }; int[] mb6Price = { 130, 250, 120, 160 };
string[] gpu6Names = { "RTX 3060", "RTX 3080", "RX 6600", "RX 6700 XT" };
int[] gpu6Tdp = { 170, 320, 132, 230 }; int[] gpu6Score = { 75, 95, 70, 82 };
int[] gpu6Price = { 320, 700, 270, 400 }; int[] gpu6Brand = { 1, 1, 0, 0 };
string[] psu6Names = { "Corsair CV 550W", "Corsair RM 750W", "BeQuiet 850W" };
int[] psu6Watt = { 550, 750, 850 }; int[] psu6Price = { 75, 110, 140 }; int[] psu6Warranty = { 3, 7, 5 };
string[] case6Names = { "NZXT H510", "Lian Li O11 Blanc", "Fractal Pop Rouge" };
int[] case6Color = { 0, 1, 2 }; int[] case6Price = { 90, 140, 100 };
int BUDGET6 = 820; // EUR (serre, comme dans le jumeau Python)
int MIN_PERF6 = 145; // score CPU + GPU minimum
int M6 = 5; // facteur lexicographique : M > max violations WEAK (4)
// ---- Variables de decision ----
IntVar cpu6 = model6.intVar("cpu", 0, cpu6Names.Length - 1);
IntVar mb6 = model6.intVar("mb", 0, mb6Names.Length - 1);
IntVar gpu6 = model6.intVar("gpu", 0, gpu6Names.Length - 1);
IntVar psu6 = model6.intVar("psu", 0, psu6Names.Length - 1);
IntVar boitier6 = model6.intVar("boitier", 0, case6Names.Length - 1);
// ---- Attributs (element = table indexee par la variable) ----
IntVar cpuSocket6 = model6.intVar("cpu_socket", 0, 1); model6.element(cpuSocket6, cpu6Socket, cpu6).post();
IntVar cpuTdp6 = model6.intVar("cpu_tdp", 0, 400); model6.element(cpuTdp6, cpu6Tdp, cpu6).post();
IntVar cpuScore6 = model6.intVar("cpu_score", 0, 100); model6.element(cpuScore6, cpu6Score, cpu6).post();
IntVar cpuPrice6 = model6.intVar("cpu_price", 0, 500); model6.element(cpuPrice6, cpu6Price, cpu6).post();
IntVar cpuBrand6 = model6.intVar("cpu_brand", 0, 1); model6.element(cpuBrand6, cpu6Brand, cpu6).post();
IntVar mbSocket6 = model6.intVar("mb_socket", 0, 1); model6.element(mbSocket6, mb6Socket, mb6).post();
IntVar mbPrice6 = model6.intVar("mb_price", 0, 400); model6.element(mbPrice6, mb6Price, mb6).post();
IntVar gpuTdp6 = model6.intVar("gpu_tdp", 0, 400); model6.element(gpuTdp6, gpu6Tdp, gpu6).post();
IntVar gpuScore6 = model6.intVar("gpu_score", 0, 100); model6.element(gpuScore6, gpu6Score, gpu6).post();
IntVar gpuPrice6 = model6.intVar("gpu_price", 0, 800); model6.element(gpuPrice6, gpu6Price, gpu6).post();
IntVar gpuBrand6 = model6.intVar("gpu_brand", 0, 1); model6.element(gpuBrand6, gpu6Brand, gpu6).post();
IntVar psuWatt6 = model6.intVar("psu_watt", 0, 1000); model6.element(psuWatt6, psu6Watt, psu6).post();
IntVar psuPrice6 = model6.intVar("psu_price", 0, 200); model6.element(psuPrice6, psu6Price, psu6).post();
IntVar psuWarranty6 = model6.intVar("psu_warranty", 0, 10); model6.element(psuWarranty6, psu6Warranty, psu6).post();
IntVar caseColor6 = model6.intVar("case_color", 0, 2); model6.element(caseColor6, case6Color, boitier6).post();
IntVar casePrice6 = model6.intVar("case_price", 0, 200); model6.element(casePrice6, case6Price, boitier6).post();
IntVar totalPrice6 = model6.intVar("total_price", 0, 5000);
IntVar totalScore6 = model6.intVar("total_score", 0, 200);
IntVar totalTdp6 = model6.intVar("total_tdp", 0, 700);
model6.sum(new IntVar[] { cpuPrice6, mbPrice6, gpuPrice6, psuPrice6, casePrice6 }, "=", totalPrice6).post();
model6.sum(new IntVar[] { cpuScore6, gpuScore6 }, "=", totalScore6).post();
model6.sum(new IntVar[] { cpuTdp6, gpuTdp6 }, "=", totalTdp6).post();
// ---- MANDATORY : contraintes dures, postees (compatibilite socket, alimentation) ----
model6.arithm(cpuSocket6, "=", mbSocket6).post();
IntVar minWatt6 = model6.intVar("min_watt", 80, 780);
model6.arithm(minWatt6, "=", totalTdp6, "+", 80).post();
model6.arithm(psuWatt6, ">=", minWatt6).post();
// ---- STRONG : reifiees completement (ifThenElse = OnlyEnforceIf/Not de CP-SAT) ----
var overBudget6 = model6.boolVar("over_budget");
model6.ifThenElse(overBudget6,
model6.arithm(totalPrice6, ">", BUDGET6),
model6.arithm(totalPrice6, "<=", BUDGET6));
var lowPerf6 = model6.boolVar("low_perf");
model6.ifThenElse(lowPerf6,
model6.arithm(totalScore6, "<", MIN_PERF6),
model6.arithm(totalScore6, ">=", MIN_PERF6));
// ---- WEAK : souhaits reifies ----
var wrongCpuBrand6 = model6.boolVar("wrong_cpu_brand");
model6.ifThenElse(wrongCpuBrand6,
model6.arithm(cpuBrand6, "!=", 0),
model6.arithm(cpuBrand6, "=", 0));
var wrongGpuBrand6 = model6.boolVar("wrong_gpu_brand");
model6.ifThenElse(wrongGpuBrand6,
model6.arithm(gpuBrand6, "!=", 0),
model6.arithm(gpuBrand6, "=", 0));
var wrongColor6 = model6.boolVar("wrong_color");
model6.ifThenElse(wrongColor6,
model6.arithm(caseColor6, "!=", 0),
model6.arithm(caseColor6, "=", 0));
var shortWarranty6 = model6.boolVar("short_warranty");
model6.ifThenElse(shortWarranty6,
model6.arithm(psuWarranty6, "<", 7),
model6.arithm(psuWarranty6, ">=", 7));
// ---- Objectif lexicographique linearise : M*strong + weak (scalar, cf. section 1.3) ----
IntVar strongCost6 = model6.intVar("strong_cost", 0, 2);
model6.sum(new IntVar[] { overBudget6, lowPerf6 }, "=", strongCost6).post();
IntVar weakCost6 = model6.intVar("weak_cost", 0, 4);
model6.sum(new IntVar[] { wrongCpuBrand6, wrongGpuBrand6, wrongColor6, shortWarranty6 }, "=", weakCost6).post();
IntVar hierCost6 = model6.intVar("hier_cost", 0, M6 * 2 + 4);
model6.scalar(new IntVar[] { strongCost6, weakCost6 }, new int[] { M6, 1 }, "=", hierCost6).post();
model6.setObjective(false, hierCost6); // false = MINIMIZE
var sw6 = System.Diagnostics.Stopwatch.StartNew();
var solver6 = model6.getSolver();
int bestCost6 = int.MaxValue, bc6 = 0, bm6 = 0, bg6 = 0, bp6 = 0, bk6 = 0, bStrong6 = 0, bWeak6 = 0;
while (solver6.solve()) {
bestCost6 = hierCost6.getValue();
bStrong6 = strongCost6.getValue(); bWeak6 = weakCost6.getValue();
bc6 = cpu6.getValue(); bm6 = mb6.getValue(); bg6 = gpu6.getValue();
bp6 = psu6.getValue(); bk6 = boitier6.getValue();
}
sw6.Stop();
string[] brands6 = { "AMD", "Intel/Nvidia" };
string[] colors6 = { "noir", "blanc", "rouge" };
int total6 = cpu6Price[bc6] + mb6Price[bm6] + gpu6Price[bg6] + psu6Price[bp6] + case6Price[bk6];
int score6 = cpu6Score[bc6] + gpu6Score[bg6];
int tdp6 = cpu6Tdp[bc6] + gpu6Tdp[bg6];
Console.WriteLine("Configuration PC optimale (Choco, " + sw6.ElapsedMilliseconds + " ms)");
Console.WriteLine();
Console.WriteLine($" CPU : {cpu6Names[bc6],-28} {cpu6Price[bc6],4} EUR score={cpu6Score[bc6]} {brands6[cpu6Brand[bc6]]}");
Console.WriteLine($" Carte mere : {mb6Names[bm6],-28} {mb6Price[bm6],4} EUR");
Console.WriteLine($" GPU : {gpu6Names[bg6],-28} {gpu6Price[bg6],4} EUR score={gpu6Score[bg6]} {brands6[gpu6Brand[bg6]]}");
Console.WriteLine($" Alimentation: {psu6Names[bp6],-28} {psu6Price[bp6],4} EUR {psu6Watt[bp6]}W garantie {psu6Warranty[bp6]} ans");
Console.WriteLine($" Boitier : {case6Names[bk6],-28} {case6Price[bk6],4} EUR {colors6[case6Color[bk6]]}");
Console.WriteLine();
Console.WriteLine("=== Bilan ===");
Console.WriteLine($" Prix total : {total6} EUR (budget {BUDGET6}) => {(total6 <= BUDGET6 ? "OK" : "DEPASSE de " + (total6 - BUDGET6) + " EUR")}");
Console.WriteLine($" Score total : {score6} (min {MIN_PERF6}) => {(score6 >= MIN_PERF6 ? "OK" : "INSUFFISANT")}");
Console.WriteLine($" TDP total : {tdp6}W + 80W = {tdp6 + 80}W (alim {psu6Watt[bp6]}W)");
Console.WriteLine();
Console.WriteLine("Contraintes souples");
Console.WriteLine($" [STRONG] Budget : {(total6 <= BUDGET6 ? "satisfaite" : "VIOLEE")}");
Console.WriteLine($" [STRONG] Perf min : {(score6 >= MIN_PERF6 ? "satisfaite" : "VIOLEE")}");
Console.WriteLine($" [WEAK] Marque CPU : {brands6[cpu6Brand[bc6]]} {(cpu6Brand[bc6] == 0 ? "(prefere)" : "(non prefere -- compromis budget/perf)")}");
Console.WriteLine($" [WEAK] Marque GPU : {brands6[gpu6Brand[bg6]]} {(gpu6Brand[bg6] == 0 ? "(prefere)" : "(non prefere -- compromis budget/perf)")}");
Console.WriteLine($" [WEAK] Couleur : {colors6[case6Color[bk6]]} {(case6Color[bk6] == 0 ? "(prefere)" : "(non prefere)")}");
Console.WriteLine($" [WEAK] Garantie : {psu6Warranty[bp6]} ans {(psu6Warranty[bp6] >= 7 ? "(OK)" : "(courte)")}");
Console.WriteLine();
Console.WriteLine($" Score objectif : {M6} x {bStrong6} (strong) + {bWeak6} (weak) = {bestCost6}");
Console.WriteLine(" Accord cross-moteurs : le jumeau Python (CP-SAT) trouve le meme objectif = 3 sur le meme catalogue.");