import heapq
import itertools
def successeurs(problem, state):
"""Etats successeurs d'un etat (semantique delete-free de STRIPSProblem :
on accumule les add_effects des actions applicables). Renvoie (action, nouvel_etat)."""
resultats = []
for action in problem.actions:
if action.preconditions.issubset(state):
nouvel_etat = state | action.add_effects
if nouvel_etat != state: # ignorer les no-ops (faits deja presents)
resultats.append((action, nouvel_etat))
return resultats
def recherche(problem, h_fn=None, mode="ucs"):
"""Recherche dans le graphe d'etats. mode='ucs' (aveugle, h=0), 'astar' (g+h),
'greedy' (h seul). Renvoie (cout_solution, noeuds_developpes). Un noeud est
'developpe' quand il est sorti de la frontiere et expansé (successeurs generes)."""
def cle(g, h):
if mode == "ucs": return (g,)
if mode == "astar": return (g + h, g)
if mode == "greedy": return (h, g)
compteur = itertools.count()
depart = frozenset(problem.initial_state)
h0 = h_fn(problem, set(depart)) if h_fn else 0
frontiere = [(cle(0, h0), next(compteur), 0, h0, depart)]
fermes = set()
developpes = 0
while frontiere:
_, _, g, h, etat = heapq.heappop(frontiere)
if etat in fermes:
continue
fermes.add(etat)
developpes += 1
if problem.goal.issubset(etat):
return g, developpes
for action, suivant in successeurs(problem, etat):
if suivant in fermes:
continue
ng = g + action.cost
nh = h_fn(problem, set(suivant)) if h_fn else 0
heapq.heappush(frontiere, (cle(ng, nh), next(compteur), ng, nh, suivant))
return None, developpes
def probleme_chaine_avec_distracteurs(M, D):
"""But : realiser une chaine c0 -> c1 -> ... -> cM (cout optimal M). La
bibliotheque d'actions contient en PLUS D chaines 'leurres' d_j : c0 -> d_j ->
dd_j qui ne contribuent PAS au but. Ces faits/actions non pertinents sont
omnipresents dans les vrais domaines : un planificateur aveugle perd du temps a
les explorer, une recherche guidee par heuristique les ignore."""
actions = [STRIPSAction(f"etape_but_{j+1}", {f"c{j}"}, {f"c{j+1}"}, 1) for j in range(M)]
for j in range(D):
actions.append(STRIPSAction(f"leurre_{j}_1", {"c0"}, {f"d{j}"}, 1))
actions.append(STRIPSAction(f"leurre_{j}_2", {f"d{j}"}, {f"dd{j}"}, 1))
return STRIPSProblem({"c0"}, {f"c{M}"}, actions)
# Wrappers : h_max renvoie un int, mais h_add/h_ff renvoient (valeur, helpful_actions).
h_max_pur = lambda p, s: h_max(p, s)
h_add_pur = lambda p, s: h_add(p, s)[0]
h_ff_pur = lambda p, s: h_ff(p, s)[0]
print("Benchmark : nombre de NOEUDS DEVELOPPES (deterministe, reproductible)")
print("But = chaine de longueur M ; D = chaines leurres (faits non pertinents)\n")
print(f"{'M':>3} {'D':>3} | {'UCS (aveugle)':>14} {'A*+h^max':>10} {'A*+h^add':>10} {'Greedy+h^FF':>12}")
print("-" * 62)
for (M, D) in [(3, 0), (3, 4), (3, 8), (3, 12), (3, 16)]:
p = probleme_chaine_avec_distracteurs(M, D)
_, ucs = recherche(p, None, "ucs")
_, amax = recherche(p, h_max_pur, "astar")
_, aadd = recherche(p, h_add_pur, "astar")
_, gff = recherche(p, h_ff_pur, "greedy")
print(f"{M:>3} {D:>3} | {ucs:>14} {amax:>10} {aadd:>10} {gff:>12}")