# Demonstration : sur une instance non-triviale, la relaxation lineaire de CP-SAT fait son travail
# La cellule precedente comparait 3 strategies sur un Job-Shop trivial (3 jobs) : toutes
# donnaient makespan=11 avec ~13 branches et 0 conflit -- l'instance est trop petite pour
# que les parametres discriminant. Verifions la prediction de l'interpretation ci-dessus :
# "la relaxation lineaire aide surtout sur les grands problemes" et "2 a 10x de sensibilite".
import time
from ortools.sat.python import cp_model as _cp
# Instance 10x10 (10 jobs, 10 machines, durees 1..99) -- beaucoup plus representative
# qu'un banc pedagogique de 3 jobs.
INSTANCE_10x10 = [
[(6, 27), (8, 13), (9, 63), (7, 4), (5, 50), (3, 56), (0, 78), (4, 98), (1, 99), (2, 1)],
[(1, 84), (6, 70), (9, 2), (5, 49), (2, 88), (0, 28), (8, 55), (3, 93), (4, 4), (7, 68)],
[(9, 98), (0, 59), (6, 38), (5, 3), (2, 54), (1, 72), (4, 83), (8, 13), (7, 24), (3, 81)],
[(8, 39), (0, 37), (2, 76), (3, 64), (9, 65), (6, 51), (7, 76), (5, 5), (1, 62), (4, 32)],
[(5, 21), (3, 67), (1, 51), (0, 48), (8, 63), (4, 94), (7, 4), (2, 61), (9, 6), (6, 40)],
[(5, 52), (7, 66), (3, 45), (0, 74), (8, 46), (4, 59), (1, 35), (2, 85), (6, 71), (9, 78)],
[(7, 47), (5, 73), (9, 71), (3, 26), (1, 65), (8, 53), (4, 63), (2, 46), (6, 54), (0, 45)],
[(6, 71), (7, 75), (2, 24), (1, 12), (9, 71), (4, 33), (3, 5), (5, 87), (8, 10), (0, 11)],
[(4, 45), (3, 38), (8, 9), (5, 22), (1, 21), (2, 33), (6, 68), (9, 22), (7, 85), (0, 35)],
[(8, 44), (2, 54), (1, 25), (9, 34), (0, 14), (6, 33), (3, 94), (5, 66), (7, 27), (4, 78)],
]
def resoudre_jsp(jobs_data, etiquette, **params):
"""Resout un Job-Shop (ordonnancement) avec CP-SAT sous des parametres donnes."""
horizon = sum(d for job in jobs_data for _, d in job)
model = _cp.CpModel()
taches = {}; machine_vers_intervalles = {}
for jid, job in enumerate(jobs_data):
for tid, (machine, duree) in enumerate(job):
s = model.NewIntVar(0, horizon, f"s_{jid}_{tid}")
e = model.NewIntVar(0, horizon, f"e_{jid}_{tid}")
iv = model.NewIntervalVar(s, duree, e, f"iv_{jid}_{tid}")
taches[(jid, tid)] = (s, e)
machine_vers_intervalles.setdefault(machine, []).append(iv)
# precedence intra-job
for jid, job in enumerate(jobs_data):
for tid in range(len(job) - 1):
model.Add(taches[(jid, tid + 1)][0] >= taches[(jid, tid)][1])
# une machine traite une tache a la fois
for ivs in machine_vers_intervalles.values():
model.AddNoOverlap(ivs)
makespan = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(makespan, [taches[(j, len(jobs_data[j]) - 1)][1] for j in range(len(jobs_data))])
model.Minimize(makespan)
solver = _cp.CpSolver()
for k, v in params.items():
setattr(solver.parameters, k, v)
# REPRODUCTIBILITE : CP-SAT est non-deterministe en multi-worker. On force 1 worker
# et une graine fixe pour que les compteurs (branches/conflits) soient reproductibles
# d'une execution a l'autre -- condition sine qua non pour qu'une comparaison de
# parametres ait un sens pedagogique (sinon l'ecart mesure melange effet du parametre
# et bruit du parallelisme).
solver.parameters.num_search_workers = 1
solver.parameters.random_seed = 42
t0 = time.time(); status = solver.Solve(model); elapsed = time.time() - t0
return {
"strategie": etiquette,
"makespan": solver.Value(makespan) if status in (_cp.OPTIMAL, _cp.FEASIBLE) else None,
"temps": elapsed,
"branches": solver.NumBranches(),
"conflits": solver.NumConflicts(),
}
strategies = [
("Default", {}),
("linearization_level=0", {"linearization_level": 0}),
("max_presolve_iterations=10", {"max_presolve_iterations": 10}),
]
print("Comparaison des strategies sur un Job-Shop 10x10 (instance non-triviale)")
print("=" * 80)
print(f"{'Strategie':>26} | {'Makespan':>8} | {'Temps (s)':>9} | {'Branches':>9} | {'Conflits':>8}")
print("-" * 80)
resultats = [resoudre_jsp(INSTANCE_10x10, nom, **p) for nom, p in strategies]
for r in resultats:
print(f"{r['strategie']:>26} | {str(r['makespan']):>8} | {r['temps']:>9.4f} | {r['branches']:>9} | {r['conflits']:>8}")