import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from collections import defaultdict
class BacktrackingVisualizer:
"""
Visualiseur de l'arbre de recherche du backtracking.
Enregistre chaque assignation et backtrack pour affichage.
"""
def __init__(self):
self.nodes = [] # Liste des (profondeur, variable, valeur, status)
self.edges = [] # Liste des (parent_idx, child_idx)
self.current_path = [] # Chemin actuel dans l'arbre
def record_assign(self, var, value, success):
"""Enregistre une tentative d'assignation."""
depth = len(self.current_path)
node_id = len(self.nodes)
status = 'success' if success else 'fail'
self.nodes.append((depth, var, value, status))
if self.current_path:
parent_id = self.current_path[-1]
self.edges.append((parent_id, node_id))
if success:
self.current_path.append(node_id)
return node_id
def record_backtrack(self):
"""Enregistre un backtrack."""
if self.current_path:
self.current_path.pop()
def draw(self, max_depth=6, title="Arbre de Backtracking"):
"""Dessine l'arbre de recherche."""
fig, ax = plt.subplots(1, 1, figsize=(14, 8))
if not self.nodes:
ax.text(0.5, 0.5, "Aucun noeud a afficher", ha='center', va='center')
ax.set_title(title)
return
# Filtrer par profondeur
filtered_nodes = [(i, n) for i, n in enumerate(self.nodes) if n[0] <= max_depth]
# Positionnement des noeuds
depth_nodes = defaultdict(list)
for idx, (depth, var, value, status) in filtered_nodes:
depth_nodes[depth].append((idx, var, value, status))
positions = {}
max_width = max(len(nodes) for nodes in depth_nodes.values()) if depth_nodes else 1
for depth, nodes_at_depth in depth_nodes.items():
n = len(nodes_at_depth)
for i, (idx, var, value, status) in enumerate(nodes_at_depth):
x = (i - (n-1)/2) / max_width * 10
y = -depth
positions[idx] = (x, y)
# Dessiner les aretes
for parent, child in self.edges:
if parent in positions and child in positions:
x1, y1 = positions[parent]
x2, y2 = positions[child]
ax.plot([x1, x2], [y1, y2], 'k-', alpha=0.3, linewidth=0.5)
# Dessiner les noeuds
colors = {'success': '#4CAF50', 'fail': '#f44336', 'pruned': '#FF9800'}
for idx, (depth, var, value, status) in filtered_nodes:
x, y = positions[idx]
color = colors.get(status, '#2196F3')
ax.scatter(x, y, c=color, s=100, zorder=5)
ax.annotate(f"{var}={value}", (x, y),
xytext=(0, 10), textcoords='offset points',
ha='center', fontsize=8)
# Legende
legend_patches = [mpatches.Patch(color=c, label=l)
for l, c in colors.items()]
ax.legend(handles=legend_patches, loc='upper right')
ax.set_title(title)
ax.set_xlabel("Largeur de l'arbre")
ax.set_ylabel("Profondeur")
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
plt.tight_layout()
plt.show()
def backtracking_with_viz(csp, assignment=None, viz=None, depth=0):
"""
Backtracking avec visualisation de l'arbre de recherche.
"""
if assignment is None:
assignment = {}
if viz is None:
viz = BacktrackingVisualizer()
# Verifier si toutes les variables sont assignees
if len(assignment) == len(csp.variables):
return assignment, viz
# Choisir la prochaine variable (ordre simple)
unassigned = [v for v in csp.variables if v not in assignment]
var = unassigned[0]
for value in csp.domains[var]:
# Verifier la consistance via la methode CSP
is_ok = csp.consistent(var, value, assignment)
viz.record_assign(var, value, is_ok)
if is_ok:
assignment[var] = value
result, viz = backtracking_with_viz(csp, assignment, viz, depth+1)
if result is not None:
return result, viz
del assignment[var]
viz.record_backtrack()
return None, viz
print("Visualiseur de backtracking pret.")