ia-guiao-sobre-pesquisa/tree_search.py

160 lines
5.3 KiB
Python
Raw Permalink Normal View History

2024-09-17 10:51:48 +00:00
# Module: tree_search
#
2024-09-17 10:51:48 +00:00
# This module provides a set o classes for automated
# problem solving through tree search:
# SearchDomain - problem domains
# SearchProblem - concrete problems to be solved
# SearchNode - search tree nodes
# SearchTree - search tree with the necessary methods for searhing
#
# (c) Luis Seabra Lopes
# Introducao a Inteligencia Artificial, 2012-2020,
# Inteligência Artificial, 2014-2023
from abc import ABC, abstractmethod
# Dominios de pesquisa
# Permitem calcular
# as accoes possiveis em cada estado, etc
class SearchDomain(ABC):
# construtor
@abstractmethod
def __init__(self):
pass
# lista de accoes possiveis num estado
@abstractmethod
def actions(self, state):
pass
# resultado de uma accao num estado, ou seja, o estado seguinte
@abstractmethod
def result(self, state, action):
pass
# custo de uma accao num estado
@abstractmethod
def cost(self, state, action):
pass
# custo estimado de chegar de um estado a outro
@abstractmethod
def heuristic(self, state, goal):
pass
# test if the given "goal" is satisfied in "state"
@abstractmethod
def satisfies(self, state, goal):
pass
# Problemas concretos a resolver
# dentro de um determinado dominio
class SearchProblem:
def __init__(self, domain, initial, goal):
self.domain = domain
self.initial = initial
self.goal = goal
def goal_test(self, state):
return self.domain.satisfies(state,self.goal)
# Nos de uma arvore de pesquisa
class SearchNode:
def __init__(self,state,parent, depth, cost=0, heuristic=0):
2024-09-17 10:51:48 +00:00
self.state = state
self.parent = parent
self.depth = depth
self.cost = cost
self.heuristic = heuristic
2024-09-17 10:51:48 +00:00
def __str__(self):
return "no(" + str(self.state) + "," + str(self.parent) + ")"
2024-09-17 10:51:48 +00:00
def __repr__(self):
return str(self)
# Arvores de pesquisa
class SearchTree:
# construtor
def __init__(self,problem, strategy='breadth'):
2024-09-17 10:51:48 +00:00
self.problem = problem
root = SearchNode(problem.initial, None, 0)
2024-09-17 10:51:48 +00:00
self.open_nodes = [root]
self.strategy = strategy
self.solution = None
self.terminals = 0
self.non_terminals = 0
self.highest_cost_nodes = [root]
self.average_depth = 0
@property
def length(self):
return self.solution.depth if self.solution else None
@property
def avg_branching(self):
return ((self.terminals + self.non_terminals) - 1) / self.non_terminals if self.non_terminals > 0 else None
2024-09-17 10:51:48 +00:00
@property
def cost(self):
return self.solution.cost if self.solution else None
2024-09-17 10:51:48 +00:00
# obter o caminho (sequencia de estados) da raiz ate um no
def get_path(self,node):
if node.parent == None:
return [node.state]
path = self.get_path(node.parent)
path += [node.state]
return(path)
# procurar a solucao
def search(self,limit=None):
2024-09-17 10:51:48 +00:00
while self.open_nodes != []:
self.terminals = len(self.open_nodes)
2024-09-17 10:51:48 +00:00
node = self.open_nodes.pop(0)
2024-09-17 10:51:48 +00:00
if self.problem.goal_test(node.state):
self.solution = node
self.average_depth = self.average_depth / (self.terminals + self.non_terminals)
2024-09-17 10:51:48 +00:00
return self.get_path(node)
self.non_terminals += 1
2024-09-17 10:51:48 +00:00
lnewnodes = []
for a in self.problem.domain.actions(node.state):
newstate = self.problem.domain.result(node.state,a)
if newstate not in self.get_path(node):
newnode = SearchNode(
newstate,
node,
node.depth+1,
node.cost+self.problem.domain.cost(node.state,a),
self.problem.domain.heuristic(newstate,self.problem.goal)
)
if not (limit != None and self.strategy == 'depth' and newnode.depth > limit):
lnewnodes.append(newnode)
if newnode.cost > self.highest_cost_nodes[0].cost:
self.highest_cost_nodes = [newnode]
elif newnode.cost == self.highest_cost_nodes[0].cost:
self.highest_cost_nodes.append(newnode)
self.average_depth += newnode.depth
2024-09-17 10:51:48 +00:00
self.add_to_open(lnewnodes)
2024-09-17 10:51:48 +00:00
return None
# juntar novos nos a lista de nos abertos de acordo com a estrategia
def add_to_open(self,lnewnodes):
if self.strategy == 'breadth':
self.open_nodes.extend(lnewnodes)
elif self.strategy == 'depth':
self.open_nodes[:0] = lnewnodes
elif self.strategy == 'uniform':
self.open_nodes = sorted(self.open_nodes + lnewnodes, key=lambda node: node.cost)
elif self.strategy == 'greedy':
self.open_nodes = sorted(self.open_nodes + lnewnodes, key=lambda node: node.heuristic)
elif self.strategy == 'a*':
self.open_nodes = sorted(self.open_nodes + lnewnodes, key=lambda node: node.cost + node.heuristic)
2024-09-17 10:51:48 +00:00