// B10 - Optimisation directe du Cmax "deux stacks" sur le meme Job Shop 3x2 (cellule 9)
// Meme instance (JobShopSchedule a 7 variables : S00..S21, Cmax), memes contraintes dures,
// memes precedents, memes non-chevauchements. La difference : au lieu d'enumerer les bornes
// T dans une boucle lineaire, on declare l'objectif "minimiser Cmax" une seule fois et on
// laisse Z3 trouver l'optimum natif en un seul Check().
// =====================================================================
// BLOC RAW (API brute Microsoft.Z3) : on instancie l'Optimize, on Assert les contraintes,
// on appelle MkMinimize(cmax) puis Check() en UNE passe.
// =====================================================================
{
using var z3 = new Microsoft.Z3.Context();
// Variables : memes noms que la classe DSL (la coherence est purement documentaire ici)
IntExpr s00 = z3.MkIntConst("S00"), s01 = z3.MkIntConst("S01");
IntExpr s10 = z3.MkIntConst("S10"), s11 = z3.MkIntConst("S11");
IntExpr s20 = z3.MkIntConst("S20"), s21 = z3.MkIntConst("S21");
IntExpr cmax = z3.MkIntConst("Cmax");
var opt = z3.MkOptimize();
// 1. Non-negativite
opt.Assert(z3.MkGe(s00, z3.MkInt(0))); opt.Assert(z3.MkGe(s01, z3.MkInt(0)));
opt.Assert(z3.MkGe(s10, z3.MkInt(0))); opt.Assert(z3.MkGe(s11, z3.MkInt(0)));
opt.Assert(z3.MkGe(s20, z3.MkInt(0))); opt.Assert(z3.MkGe(s21, z3.MkInt(0)));
// 2. Precedences (routes)
opt.Assert(z3.MkGe(s01, z3.MkAdd(s00, z3.MkInt(3)))); // J0: A(3h) avant B
opt.Assert(z3.MkGe(s10, z3.MkAdd(s11, z3.MkInt(2)))); // J1: B(2h) avant A
opt.Assert(z3.MkGe(s21, z3.MkAdd(s20, z3.MkInt(2)))); // J2: A(2h) avant B
// 3. Non-chevauchement Machine A (J0:3, J1:4, J2:2)
opt.Assert(z3.MkOr(z3.MkGe(s00, z3.MkAdd(s10, z3.MkInt(4))), z3.MkGe(s10, z3.MkAdd(s00, z3.MkInt(3)))));
opt.Assert(z3.MkOr(z3.MkGe(s00, z3.MkAdd(s20, z3.MkInt(2))), z3.MkGe(s20, z3.MkAdd(s00, z3.MkInt(3)))));
opt.Assert(z3.MkOr(z3.MkGe(s10, z3.MkAdd(s20, z3.MkInt(2))), z3.MkGe(s20, z3.MkAdd(s10, z3.MkInt(4)))));
// 4. Non-chevauchement Machine B (J0:2, J1:2, J2:3)
opt.Assert(z3.MkOr(z3.MkGe(s01, z3.MkAdd(s11, z3.MkInt(2))), z3.MkGe(s11, z3.MkAdd(s01, z3.MkInt(2)))));
opt.Assert(z3.MkOr(z3.MkGe(s01, z3.MkAdd(s21, z3.MkInt(3))), z3.MkGe(s21, z3.MkAdd(s01, z3.MkInt(2)))));
opt.Assert(z3.MkOr(z3.MkGe(s11, z3.MkAdd(s21, z3.MkInt(3))), z3.MkGe(s21, z3.MkAdd(s11, z3.MkInt(2)))));
// 5. Definition du makespan (sans borne superieure : on cherche l'optimum)
opt.Assert(z3.MkGe(cmax, z3.MkAdd(s00, z3.MkInt(3))));
opt.Assert(z3.MkGe(cmax, z3.MkAdd(s01, z3.MkInt(2))));
opt.Assert(z3.MkGe(cmax, z3.MkAdd(s10, z3.MkInt(4))));
opt.Assert(z3.MkGe(cmax, z3.MkAdd(s11, z3.MkInt(2))));
opt.Assert(z3.MkGe(cmax, z3.MkAdd(s20, z3.MkInt(2))));
opt.Assert(z3.MkGe(cmax, z3.MkAdd(s21, z3.MkInt(3))));
// 6. Objectif : minimiser Cmax (UN SEUL appel, pas de boucle T = 9..16)
opt.MkMinimize(cmax);
var status = opt.Check();
Console.WriteLine($"[RAW] Status : {status} (UN seul Check, pas de boucle lineaire)");
if (status == Microsoft.Z3.Status.SATISFIABLE)
{
var vCmax = ((Microsoft.Z3.IntNum)opt.Model.Eval(cmax, true)).Int;
Console.WriteLine($"[RAW] Cmax optimal direct = {vCmax}h (vs recherche lineaire cellule 9 : 9h au premier SAT)");
// Pour memoire : le raw access Model sur un Optimize renvoie le modele optimal,
// pas un modele intermediaire comme dans la boucle lineaire.
var vS00 = ((Microsoft.Z3.IntNum)opt.Model.Eval(s00, true)).Int;
Console.WriteLine($"[RAW] Premiere variable extraite S00 = {vS00} (le raw donne acces direct au modele)");
}
}
Console.WriteLine();
// =====================================================================
// BLOC DSL (Z3.Linq) : .Optimize(Minimize, t => t.Cmax) sur la meme classe JobShopSchedule
// Reutilise les contraintes deja ecrites en cellules 7-9 (memes .Where, meme classe).
// =====================================================================
{
using var ctx = new Z3Context();
var theorem = ctx.NewTheorem<JobShopSchedule>()
// Meme chaine de Where que la cellule 9, SAUF la derniere borne Cmax <= bound (supprimee : on minimise)
.Where(t => t.S00 >= 0 && t.S01 >= 0 && t.S10 >= 0
&& t.S11 >= 0 && t.S20 >= 0 && t.S21 >= 0 && t.Cmax >= 0)
.Where(t => t.S01 >= t.S00 + 3) // J0 : A (3h) avant B
.Where(t => t.S10 >= t.S11 + 2) // J1 : B (2h) avant A
.Where(t => t.S21 >= t.S20 + 2) // J2 : A (2h) avant B
// Machine A
.Where(t => t.S00 >= t.S10 + 4 || t.S10 >= t.S00 + 3)
.Where(t => t.S00 >= t.S20 + 2 || t.S20 >= t.S00 + 3)
.Where(t => t.S10 >= t.S20 + 2 || t.S20 >= t.S10 + 4)
// Machine B
.Where(t => t.S01 >= t.S11 + 2 || t.S11 >= t.S01 + 2)
.Where(t => t.S01 >= t.S21 + 3 || t.S21 >= t.S01 + 2)
.Where(t => t.S11 >= t.S21 + 3 || t.S21 >= t.S11 + 2)
// Definition du makespan (pas de borne sup : on laisse Z3 minimiser)
.Where(t => t.Cmax >= t.S00 + 3 && t.Cmax >= t.S01 + 2)
.Where(t => t.Cmax >= t.S10 + 4 && t.Cmax >= t.S11 + 2)
.Where(t => t.Cmax >= t.S20 + 2 && t.Cmax >= t.S21 + 3);
// === ICI : l'objectif (UN SEUL appel, pas de boucle) =====================
// Sucre LINQ : .OrderBy(lambda) renvoie ISolveable<T> (DeferredSolvable, Theorem{T}.cs:142).
// Pour obtenir le T resultat (membre direct .Cmax, .S00, etc.), on appelle .Solve()
// (idem que pour .Solve() direct sur le Theorem). C'est le meme UN appel Check en arriere-plan.
ISolveable<JobShopSchedule> optimalSolveable = theorem.OrderBy(t => t.Cmax);
JobShopSchedule optimal = optimalSolveable?.Solve();
if (optimal != null)
{
Console.WriteLine($"[DSL] Optimize(Minimize, Cmax) : Cmax optimal = {optimal.Cmax}h (meme verdict que RAW)");
Console.WriteLine($"[DSL] Planning derive : J0=A({optimal.S00}..{optimal.S00+3})->B({optimal.S01}..{optimal.S01+2}), J1=B({optimal.S11}..{optimal.S11+2})->A({optimal.S10}..{optimal.S10+4}), J2=A({optimal.S20}..{optimal.S20+2})->B({optimal.S21}..{optimal.S21+3})");
Console.WriteLine($"[DSL] -> appels API : 1 Check au lieu de N+1 (N etant la distance opt - borne inf)");
}
}
Console.WriteLine();
Console.WriteLine("B10 OK : raw (MkOptimize + MkMinimize) et DSL (.Optimize(Minimize)) produisent le meme");
Console.WriteLine("Cmax = 9h sur la meme instance, l'un en une passe optimisee, l'autre sous forme de lambda.");