# Monde des Blocs STRIPS minimal, porteur libre (on(X, ?))
# On construit le dual exact de la recherche avant : la regression (recherche arriere).
import collections
BLOCKS = ['A', 'B', 'C']
TAB = 'Table'
TARGETS = BLOCKS + [TAB] # ce sur quoi un bloc peut etre pose
def on(x, y): return ('on', x, y)
def clr(x): return ('clear', x)
def on_free(x): return ('on', x, None) # "x est pose sur un support inconnu"
def is_free(l): return l[0] == 'on' and l[2] is None
def fmt(l):
if is_free(l): return f"on({l[1]}, ?)"
if l[0] == 'clear': return f"clear({l[1]})"
return f"on({l[1]}, {l[2]})"
def fmt_set(s):
if not s: return "∅"
return "{" + ", ".join(fmt(l) for l in sorted(s, key=lambda l: (l[0], str(l[1:])))) + "}"
def subkey(s):
return (len(s), tuple(sorted((l[0], str(l[1:])) for l in s)))
# Actions schematiques move(X,Y) : X est pose sur Y (Y != X). Le support de X est libre.
def build_strips_actions():
acts = {}
for X in BLOCKS:
for Y in TARGETS:
if Y == X: continue
pre = {clr(X), on_free(X)}
if Y != TAB: pre.add(clr(Y))
add = {on(X, Y)}
dele = {on_free(X)}
if Y != TAB: dele.add(clr(Y))
acts[f"move({X},{Y})"] = {'name': f"move({X},{Y})", 'pre': pre, 'add': add, 'del': dele}
return acts
S_ACTIONS = build_strips_actions()
def lit_true(l, state):
if is_free(l):
return any(('on', l[1], z) in state for z in TARGETS if z != l[1])
return l in state
def sub_ok(sub, state):
return all(lit_true(l, state) for l in sub)
# --- cote avant ---
def applicable(a, state): return sub_ok(a['pre'], state)
def apply(a, state):
ns = set(state)
for l in a['del']:
if is_free(l): ns = {s for s in ns if not (s[0] == 'on' and s[1] == l[1])}
else: ns.discard(l)
ns |= a['add']
return frozenset(ns)
# --- cote arriere : la duale ---
def pertinente(a, but):
if not (a['add'] & but): return False
for d in a['del']:
if d in but: return False
return True
def regress(but, a):
if not pertinente(a, but): return None
r = set(but - a['add'])
for p in a['pre']: r.add(p)
return frozenset(r)
def get_predecessors(but):
return [(a['name'], regress(but, a)) for a in S_ACTIONS.values() if regress(but, a) is not None]
# Etat initial : les trois blocs poses sur la table
INIT = frozenset({on('A', TAB), on('B', TAB), on('C', TAB), clr('A'), clr('B'), clr('C')})
def bfs_forward(init, goal, maxn=5000):
vis = {frozenset(init)}
q = collections.deque([(init, [])]); nodes = 1
while q and nodes < maxn:
st, pl = q.popleft()
if goal <= st: return pl, nodes
for a in S_ACTIONS.values():
if applicable(a, st):
ns = apply(a, st)
if ns not in vis: vis.add(ns); q.append((ns, pl + [a['name']])); nodes += 1
return None, nodes
def bfs_backward(goal, init, maxn=5000):
# BFS en arriere : chaque sous-but de la file est un ensemble de litteraux a rendre vrais.
vis = {frozenset(goal)}
q = collections.deque([(goal, [])]); nodes = 1
while q and nodes < maxn:
sub, pl = q.popleft()
if sub_ok(sub, init): return pl, nodes, list(vis) # vis = tous les sous-buts rencontres
for name, r in get_predecessors(sub):
if r is None: continue
if r not in vis: vis.add(r); q.append((r, pl + [name])); nodes += 1
return None, nodes, list(vis)
# ---- Demo 1 : but a UN SEUL litteral (les autres blocs non contraints) ----
GOAL1 = frozenset({on('A', 'B')})
print("Demo 1 : but PARTIEL", fmt_set(GOAL1), "- les blocs B et C ne sont pas contraints")
print(" Initial :", fmt_set(INIT))
print(" Sous-buts rencontres :")
fwd_plan, fwd_nodes = bfs_forward(INIT, GOAL1)
bwd_plan, bwd_nodes, frontier = bfs_backward(GOAL1, INIT)
for i, s in enumerate(sorted(frontier, key=subkey)):
print(" sous-but", i+1, ":", fmt_set(s))
print(" Avant :", fwd_nodes, "noeuds, plan", fwd_plan)
print(" Arr :", bwd_nodes, "noeuds, sous-buts (plan inverse)", [a for a in bwd_plan])
# ---- Demo 2 : tour A/B/C - le plan arriere sort inverse du plan avant ----
GOAL2 = frozenset({on('A', 'B'), on('B', 'C')})
fwd2, nf2 = bfs_forward(INIT, GOAL2)
bwd2, nb2, fr2 = bfs_backward(GOAL2, INIT)
print("\nDemo 2 : but", fmt_set(GOAL2))
print(" Avant :", nf2, "noeuds, plan", fwd2)
print(" Arr :", nb2, "noeuds, plan", bwd2)
print(" Sous-buts de la demo 2 :")
for s in sorted(fr2, key=subkey):
print(" ", fmt_set(s))
print("\nBranchement depuis INIT (actions applicables) :", len([a for a in S_ACTIONS.values() if applicable(a, INIT)]))
print("Branchement depuis le but 1 (actions pertinentes) :", len(get_predecessors(GOAL1)))