commit 9e833eaf57444e0b2bb679a70010418b0b1dde50 Author: github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue Sep 17 10:51:48 2024 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2e351b --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# iia-ia-guiao-pesquisa +Guião de resolução automatica de problemas + +# Como resolver o guião +1. Crie um virtual environment: +```bash +python3 -m venv venv +``` + +2. Active o virtual environment (precisa de repetir este passo sempre que começar uma nossa sessão/terminal): +```bash +source venv/bin/activate +``` + +3. Instale os requisitos: +```bash +pip install -r requirements.txt +``` + +4. Programe a sua solução. + +5. Teste a sua solução. +```bash +pytest diff --git a/amigos.py b/amigos.py new file mode 100644 index 0000000..684eabf --- /dev/null +++ b/amigos.py @@ -0,0 +1,7 @@ +from constraintsearch import * + +amigos = ["Andre", "Bernardo", "Claudio"] + +cs = ConstraintSearch(None, None) + +print(cs.search()) diff --git a/blocksworld.py b/blocksworld.py new file mode 100644 index 0000000..7099d34 --- /dev/null +++ b/blocksworld.py @@ -0,0 +1,122 @@ +# +# Module: blocksworld +# +# Based on the imported "strips" module, the "blocksworld" module +# defines a set of predicates and operators for representing +# the "Blocks World" planning domain. +# +# (c) Luis Seabra Lopes +# Inteligência Artificial & Introducao a Inteligencia Artificial, 2019-2020 +# + + +import time +from strips import * + +# Blocks world predicates + +class Floor(Predicate): + def __init__(self,block): + self.args = [block] + +class On(Predicate): + def __init__(self,b1,b2): + self.args = [b1,b2] + +class Free(Predicate): + def __init__(self,block): + self.args = [block] + +class Holds(Predicate): + def __init__(self,block): + self.args = [block] + +class HandFree(Predicate): + def __init__(self): + self.args = [] + + +# Blocks world operators + +X='X' +Y='Y' +Z='Z' + +class Stack(Operator): + args = [X,Y] + pc = [Holds(X),Free(Y)] + neg = [Holds(X),Free(Y)] + pos = [On(X,Y),HandFree(),Free(X)] + +class Unstack(Operator): + args = [X,Y] + pc = [On(X,Y),HandFree(),Free(X)] + neg = [On(X,Y),HandFree(),Free(X)] + pos = [Holds(X),Free(Y)] + +class Putdown(Operator): + args = [X] + pc = [Holds(X)] + neg = [Holds(X)] + pos = [Floor(X),HandFree(),Free(X)] + +class Pickup(Operator): + args = [X] + pc = [Floor(X),HandFree(),Free(X)] + neg = [Floor(X),HandFree(),Free(X)] + pos = [Holds(X)] + + +a='a' +b='b' +c='c' +d='d' +e='e' + +initial_state = [ Floor(a), Floor(b), Floor(d), Holds(e), On(c,d), + Free(a), Free(b), Free(c) ] +# _ +# / \ +# | (e) +# | +# | |c| +# _|___|a|____|b|_____|d|_ +# + +goal_state = [ Floor(c), On(d,c), On(e,d), On(a,e), Floor(b) ] + +# _ +# / \ +# | ( ) |a| +# | |e| +# | |d| +# _|__________|b|___|c|___ +# + + + + +print('Substitute:',On(X,Y).substitute({ X : a, Y : b, Z : c})) + +print('Instanciate:',Stack.instanciate([a,b])) + + +bwdomain = STRIPS() + +print('Actions:',bwdomain.actions(initial_state)) + +""" +# uncomment to test + +inittime = time.time() + +p = SearchProblem(bwdomain,initial_state,goal_state) +t = SearchTree(p) +t.search() + +print(t.plan) +print('time=',time.time()-inittime) +print(len(t.open_nodes),' nodes') +""" + + diff --git a/cidades.py b/cidades.py new file mode 100644 index 0000000..572513b --- /dev/null +++ b/cidades.py @@ -0,0 +1,119 @@ +# +# Module: cidades +# +# Implements a SearchDomain for find paths between cities +# using the tree_search module +# +# (c) Luis Seabra Lopes +# Introducao a Inteligencia Artificial, 2012-2020 +# Inteligência Artificial, 2014-2023 +# + + +from tree_search import * + +class Cidades(SearchDomain): + def __init__(self,connections, coordinates): + self.connections = connections + self.coordinates = coordinates + def actions(self,city): + actlist = [] + for (C1,C2,D) in self.connections: + if (C1==city): + actlist += [(C1,C2)] + elif (C2==city): + actlist += [(C2,C1)] + return actlist + def result(self,city,action): + (C1,C2) = action + if C1==city: + return C2 + def cost(self, city, action): + pass + def heuristic(self, city, goal_city): + pass + def satisfies(self, city, goal_city): + return goal_city==city + + +cidades_portugal = Cidades( + # Ligacoes por estrada + [ + ('Coimbra', 'Leiria', 73), + ('Aveiro', 'Agueda', 35), + ('Porto', 'Agueda', 79), + ('Agueda', 'Coimbra', 45), + ('Viseu', 'Agueda', 78), + ('Aveiro', 'Porto', 78), + ('Aveiro', 'Coimbra', 65), + ('Figueira', 'Aveiro', 77), + ('Braga', 'Porto', 57), + ('Viseu', 'Guarda', 75), + ('Viseu', 'Coimbra', 91), + ('Figueira', 'Coimbra', 52), + ('Leiria', 'Castelo Branco', 169), + ('Figueira', 'Leiria', 62), + ('Leiria', 'Santarem', 78), + ('Santarem', 'Lisboa', 82), + ('Santarem', 'Castelo Branco', 160), + ('Castelo Branco', 'Viseu', 174), + ('Santarem', 'Evora', 122), + ('Lisboa', 'Evora', 132), + ('Evora', 'Beja', 105), + ('Lisboa', 'Beja', 178), + ('Faro', 'Beja', 147), + # extra + ('Braga', 'Guimaraes', 25), + ('Porto', 'Guimaraes', 44), + ('Guarda', 'Covilha', 46), + ('Viseu', 'Covilha', 57), + ('Castelo Branco', 'Covilha', 62), + ('Guarda', 'Castelo Branco', 96), + ('Lamego','Guimaraes', 88), + ('Lamego','Viseu', 47), + ('Lamego','Guarda', 64), + ('Portalegre','Castelo Branco', 64), + ('Portalegre','Santarem', 157), + ('Portalegre','Evora', 194) ], + + # City coordinates + { 'Aveiro': (41,215), + 'Figueira': ( 24, 161), + 'Coimbra': ( 60, 167), + 'Agueda': ( 58, 208), + 'Viseu': ( 104, 217), + 'Braga': ( 61, 317), + 'Porto': ( 45, 272), + 'Lisboa': ( 0, 0), + 'Santarem': ( 38, 59), + 'Leiria': ( 28, 115), + 'Castelo Branco': ( 140, 124), + 'Guarda': ( 159, 204), + 'Evora': (120, -10), + 'Beja': (125, -110), + 'Faro': (120, -250), + #extra + 'Guimaraes': ( 71, 300), + 'Covilha': ( 130, 175), + 'Lamego' : (125,250), + 'Portalegre': (130,170) } + ) + + + + +p = SearchProblem(cidades_portugal,'Braga','Faro') +t = SearchTree(p,'breadth') + +print(t.search()) + + +# Atalho para obter caminho de c1 para c2 usando strategy: +def search_path(c1,c2,strategy): + my_prob = SearchProblem(cidades_portugal,c1,c2) + my_tree = SearchTree(my_prob) + my_tree.strategy = strategy + return my_tree.search() + + + diff --git a/constraintsearch.py b/constraintsearch.py new file mode 100644 index 0000000..db73db6 --- /dev/null +++ b/constraintsearch.py @@ -0,0 +1,56 @@ +# Pesquisa para resolucao de problemas de atribuicao +# +# Introducao a Inteligencia Artificial +# DETI / UA +# +# (c) Luis Seabra Lopes, 2012-2019 +# + + +class ConstraintSearch: + + # domains é um dicionário com o domínio de cada variável; + # constaints e' um dicionário com a restrição aplicável a cada aresta; + def __init__(self,domains,constraints): + self.domains = domains + self.constraints = constraints + self.calls = 0 + + # domains é um dicionário com os domínios actuais + # de cada variável + # ( ver acetato "Pesquisa com propagacao de restricoes + # em problemas de atribuicao - algoritmo" ) + def search(self,domains=None): + self.calls += 1 + + if domains==None: + domains = self.domains + + # se alguma variavel tiver lista de valores vazia, falha + if any([lv==[] for lv in domains.values()]): + return None + + # se nenhuma variavel tiver mais do que um valor possivel, sucesso + if all([len(lv)==1 for lv in list(domains.values())]): + # se valores violam restricoes, falha + # ( verificacao desnecessaria se for feita a propagacao + # de restricoes ) + for (var1,var2) in self.constraints: + constraint = self.constraints[var1,var2] + if not constraint(var1,domains[var1][0],var2,domains[var2][0]): + return None + return { v:lv[0] for (v,lv) in domains.items() } + + # continuação da pesquisa + # ( falta fazer a propagacao de restricoes ) + for var in domains.keys(): + if len(domains[var])>1: + for val in domains[var]: + newdomains = dict(domains) + newdomains[var] = [val] + solution = self.search(newdomains) + if solution != None: + return solution + return None + + diff --git a/guiao-pesquisa-en.pdf b/guiao-pesquisa-en.pdf new file mode 100644 index 0000000..bd4a5e4 Binary files /dev/null and b/guiao-pesquisa-en.pdf differ diff --git a/guiao-pesquisa.pdf b/guiao-pesquisa.pdf new file mode 100644 index 0000000..f6f6d8b Binary files /dev/null and b/guiao-pesquisa.pdf differ diff --git a/mapas.py b/mapas.py new file mode 100644 index 0000000..7574d97 --- /dev/null +++ b/mapas.py @@ -0,0 +1,8 @@ +from constraintsearch import * + +region = ['A', 'B', 'C', 'D', 'E'] +colors = ['red', 'blue', 'green', 'yellow', 'white'] + +cs = ConstraintSearch(None, None) + +print(cs.search()) diff --git a/rainhas.py b/rainhas.py new file mode 100644 index 0000000..b7526af --- /dev/null +++ b/rainhas.py @@ -0,0 +1,26 @@ + +from constraintsearch import * + + +def queen_constraint(r1,c1,r2,c2): + l1 = int(r1[1:]) + l2 = int(r2[1:]) + if c1==c2: + return False + if abs(l1-l2)==abs(c1-c2): + return False + return True + +def make_constraint_graph(n): + queens = [ 'R'+str(i+1) for i in range(n) ] + return { (X,Y):queen_constraint for X in queens for Y in queens if X!=Y } + +def make_domains(n): + queens = [ 'R'+str(i+1) for i in range(n) ] + cols = [ i+1 for i in range(n) ] + return { r:cols for r in queens } + +cs = ConstraintSearch(make_domains(4),make_constraint_graph(4)) + +print(cs.search()) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..93253de --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +mock +pytest diff --git a/strips.py b/strips.py new file mode 100644 index 0000000..61026dc --- /dev/null +++ b/strips.py @@ -0,0 +1,124 @@ +# +# Module: strips +# +# This module provides classes for representing STRIPS-based +# planning domains: +# Predicate - used to represent conditions in states and operators +# Operator - used to represent STRIPS operators +# STRIPS - a "SearchDomain" for planning with STRIPS operators +# +# (c) Luis Seabra Lopes +# Inteligência Artificial & Introducao a Inteligencia Artificial, 2019 +# + + +from tree_search import * +from functools import reduce +from itertools import product + + +# Predicates used to describe states, preconditions and effects +class Predicate: + def __str__(self): + argsstr = args2string(self.args) + return type(self).__name__ + "(" + argsstr + ")" + def __repr__(self): + return str(self) + def __eq__(self,predicate): # allows for comparisons with "==", etc. + return str(self)==str(predicate) + def substitute(self,assign): # Substitute the arguments in a predicate + la = self.args # by constants according to a given + if len(la)==0: # assignment (i.e. a dictionary) + return type(self)() + if len(la)==1: + return type(self)(assign[la[0]]) + # add other cases if needed + return type(self)(assign[la[0]],assign[la[1]]) + + +# STRIPS operators +# -- operators for a specific domain will be subclasses +# -- concrete actions will be instances of specific operators +class Operator: + + def __init__(self,args,pc,neg,pos): + self.args = args + self.pc = pc + self.neg = neg + self.pos = pos + def __str__(self): + return type(self).__name__ + '([' + args2string(self.args) + "]," + \ + str(self.pc) + ',' + str(self.neg) + ',' + \ + str(self.pos) + ')' + def __repr__(self): + argsstr = args2string(self.args) + return type(self).__name__ + "(" + argsstr + ")" + + # Produce a concrete action by instanciating a specific + # operator (i.e. the "Operator" subclass where the method was + # called) for the arguments given in "args" + # ( returns None if the action is not applicable in the given "state" ) + @classmethod + def instanciate(cls,args): + if len(args)!=len(cls.args): + return None + assign = dict(zip(cls.args, args)) + pc = [ p.substitute(assign) for p in cls.pc ] + neg = [ p.substitute(assign) for p in cls.neg ] + pos = [ p.substitute(assign) for p in cls.pos ] + return cls(args,pc,neg,pos) + + +# Search domains based on STRIPS actions +class STRIPS(SearchDomain): + + # constructor + def __init__(self): + pass + + # list of applicable actions in a given "state" + def actions(self, state): + constants = state_constants(state) + operators = Operator.__subclasses__() + actions = [] + for op in operators: + lassign = assignments(op.args,constants) + for assign in lassign: + argvalues = [assign[a] for a in op.args] + action = op.instanciate(argvalues) + if all(c in state for c in action.pc): + actions.append(action) + return actions + + # Result of a given "action" in a given "state" + # ( returns None, if the action is not applicable in the state) + def result(self, state, action): + pass + + def cost(self, state, action): + return 1 + + def heuristic(self, state, goal): + return 0 + + # Checks if a given "goal" is satisfied in a given "state" + def satisfies(self, state, goal): + pass + + + +# Auxiliary functions + +def state_constants(state): + return list(set(reduce(lambda r,h : h.args+r, state,[]))) + +def assignments(lvars,lconsts): + lcombs = product(lconsts,repeat=len(lvars)) + makeassign = lambda comb : dict(zip(lvars,comb)) + return list(map(makeassign,lcombs)) + +def args2string(args): + if args == []: + return "" + return reduce(lambda r,h : r+','+str(h), args[1:],str(args[0])) + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..7b49369 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,7 @@ +import os +import sys +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH,"." +) +sys.path.append(SOURCE_PATH) diff --git a/tests/test_aula3.py b/tests/test_aula3.py new file mode 100644 index 0000000..a14f4b0 --- /dev/null +++ b/tests/test_aula3.py @@ -0,0 +1,51 @@ +import pytest +from cidades import SearchProblem, SearchTree, cidades_portugal + +@pytest.fixture +def braga_faro(): + return SearchProblem(cidades_portugal,'Braga','Faro') + +def test_exercicio1(braga_faro): + t = SearchTree(braga_faro,'depth') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Castelo Branco', 'Santarem', 'Lisboa', 'Evora', 'Beja', 'Faro'] + +def test_exercicio2(braga_faro): + t = SearchTree(braga_faro, 'depth') + + assert t.open_nodes[-1].depth == 0 + t.search() + assert t.solution.depth == 11 + +def test_exercicio3(braga_faro): + t = SearchTree(braga_faro, 'depth') + + t.search() + + assert t.length == 11 + +def test_exercicio4(braga_faro): + t = SearchTree(braga_faro, 'depth') + + assert t.search(limit=9) == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Santarem', 'Lisboa', 'Beja', 'Faro'] + + assert t.length <= 9 + +def test_exercicio5(braga_faro): + t = SearchTree(braga_faro, 'depth') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Castelo Branco', 'Santarem', 'Lisboa', 'Evora', 'Beja', 'Faro'] + assert t.terminals == 19 + assert t.non_terminals == 11 + + t = SearchTree(braga_faro, 'depth') + + assert t.search(limit=9) == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Santarem', 'Lisboa', 'Beja', 'Faro'] + assert t.terminals == 12 + assert t.non_terminals == 58 + +def test_exercicio6(braga_faro): + t = SearchTree(braga_faro, 'depth') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Castelo Branco', 'Santarem', 'Lisboa', 'Evora', 'Beja', 'Faro'] + assert round(t.avg_branching,2) == round((19+11-1)/11,2) diff --git a/tests/test_aula4.py b/tests/test_aula4.py new file mode 100644 index 0000000..a9e32c3 --- /dev/null +++ b/tests/test_aula4.py @@ -0,0 +1,30 @@ +import pytest +from cidades import SearchProblem, SearchTree, cidades_portugal + +@pytest.fixture +def braga_faro(): + return SearchProblem(cidades_portugal,'Braga','Faro') + +def test_exercicio7(braga_faro): + assert cidades_portugal.cost('Aveiro', ('Aveiro', 'Agueda')) == 35 + assert cidades_portugal.cost('Agueda', ('Agueda', 'Aveiro')) == 35 + assert cidades_portugal.cost('Aveiro', ('Aveiro', 'Lisboa')) == None + +def test_exercicio8(braga_faro): + t = SearchTree(braga_faro, 'depth') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Castelo Branco', 'Santarem', 'Lisboa', 'Evora', 'Beja', 'Faro'] + assert t.solution.cost == 1104 + +def test_exercicio9(braga_faro): + t = SearchTree(braga_faro, 'depth') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Castelo Branco', 'Santarem', 'Lisboa', 'Evora', 'Beja', 'Faro'] + assert t.cost == 1104 + +def test_exercicio10(braga_faro): + t = SearchTree(braga_faro, 'uniform') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Coimbra', 'Leiria', 'Santarem', 'Evora', 'Beja', 'Faro'] + assert t.cost == 706 + assert t.length == 8 diff --git a/tests/test_aula5.py b/tests/test_aula5.py new file mode 100644 index 0000000..2ba0866 --- /dev/null +++ b/tests/test_aula5.py @@ -0,0 +1,48 @@ +import pytest +from cidades import SearchProblem, SearchTree, cidades_portugal + +@pytest.fixture +def braga_faro(): + return SearchProblem(cidades_portugal,'Braga','Faro') + +def test_exercicio11(braga_faro): + assert round(cidades_portugal.heuristic('Aveiro', 'Agueda'),2) == 18.38 + assert round(cidades_portugal.heuristic('Agueda', 'Aveiro'),2) == 18.38 + assert round(cidades_portugal.heuristic('Aveiro', 'Lisboa'),2) == 218.87 + +def test_exercicio12(braga_faro): + t = SearchTree(braga_faro, 'depth') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Aveiro', 'Coimbra', 'Leiria', 'Castelo Branco', 'Santarem', 'Lisboa', 'Evora', 'Beja', 'Faro'] + assert t.solution.heuristic == 0 + + assert t.solution.parent.state == 'Beja' + assert round(t.solution.parent.heuristic, 2) == 140.09 + +def test_exercicio13(braga_faro): + t = SearchTree(braga_faro, 'greedy') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Coimbra', 'Leiria', 'Santarem', 'Evora', 'Beja', 'Faro'] + assert t.cost == 706 + assert t.length == 8 + assert round(t.avg_branching,2) == round((17+8-1)/8,2) + +def test_exercicio14(braga_faro): + t = SearchTree(braga_faro, 'a*') + + assert t.search() == ['Braga', 'Porto', 'Agueda', 'Coimbra', 'Leiria', 'Santarem', 'Evora', 'Beja', 'Faro'] + assert t.cost == 706 + assert t.length == 8 + assert round(t.avg_branching,2) == round((160+84-1)/84,2) + +def test_exercicio15(braga_faro): + t = SearchTree(braga_faro, 'uniform') + t.search() + assert len(t.highest_cost_nodes) == 5 + assert [t.get_path(n) for n in t.highest_cost_nodes] == [['Braga', 'Porto', 'Agueda', 'Viseu', 'Castelo Branco', 'Santarem', 'Portalegre', 'Evora'], ['Braga', 'Guimaraes', 'Lamego', 'Viseu', 'Coimbra', 'Agueda', 'Aveiro', 'Figueira', 'Leiria', 'Santarem', 'Portalegre', 'Evora'], ['Braga', 'Guimaraes', 'Lamego', 'Viseu', 'Guarda', 'Castelo Branco', 'Santarem', 'Lisboa', 'Evora', 'Portalegre'], ['Braga', 'Porto', 'Agueda', 'Coimbra', 'Leiria', 'Castelo Branco', 'Santarem', 'Evora', 'Portalegre'], ['Braga', 'Porto', 'Aveiro', 'Figueira', 'Leiria', 'Coimbra', 'Agueda', 'Viseu', 'Guarda', 'Castelo Branco', 'Portalegre', 'Evora']] + +def test_exercicio16(braga_faro): + t = SearchTree(braga_faro, 'uniform') + t.search() + assert round(t.average_depth,2) == 9.02 + diff --git a/tests/test_constraints.py b/tests/test_constraints.py new file mode 100644 index 0000000..3dce18f --- /dev/null +++ b/tests/test_constraints.py @@ -0,0 +1,26 @@ +import pytest +import mapas +import amigos + +def test_exercicio1_4(): + assert mapas.cs.search() == {'A': 'red', 'B': 'blue', 'C': 'red', 'D': 'blue', 'E': 'green'} + +def test_exercicio1_5(): + solution = amigos.cs.search() + + for amigo, (bicicleta, chapeu) in solution.items(): + assert amigo != bicicleta + assert amigo != chapeu + if chapeu == "Claudio": + assert bicicleta == "Bernardo" + + bicicletas = [ bicicleta for _, (bicicleta, _) in solution.items() ] + assert len(bicicletas) == len(set(bicicletas)) + + chapeus = [ chapeu for _, (_, chapeu) in solution.items() ] + assert len(chapeus) == len(set(chapeus)) + + +def test_exercicio2(): + assert amigos.cs.calls == 14 + diff --git a/tests/test_strips.py b/tests/test_strips.py new file mode 100644 index 0000000..7dc27ef --- /dev/null +++ b/tests/test_strips.py @@ -0,0 +1,34 @@ +import pytest +from blocksworld import Floor, Holds, On, Free, a, b, c, d, e, Stack, Putdown, HandFree +from strips import STRIPS +from tree_search import SearchProblem, SearchTree + +@pytest.fixture +def initial_state(): + return [ Floor(a), Floor(b), Floor(d), Holds(e), On(c,d), Free(a), Free(b), Free(c) ] + +@pytest.fixture +def goal_state(): + return [ Floor(c), On(d,c), On(e,d), On(a,e), Floor(b) ] + +def test_exercicio1(initial_state): + bwdomain = STRIPS() + + actions = bwdomain.actions(initial_state) + + assert all(op in str(actions) for op in ["Stack(e,b)", "Stack(e,a)", "Stack(e,c)", "Putdown(e)"]) + + assert bwdomain.result(initial_state, actions[-1]) == {Free(e), On(c,d), Floor(d), Floor(b), HandFree(), Floor(a), Free(a), Free(c), Free(b), Floor(e)} + + assert bwdomain.satisfies(initial_state, [On(c,d), Free(a)]) + +def test_exercicio2(initial_state, goal_state): + bwdomain = STRIPS() + + p = SearchProblem(bwdomain,initial_state,goal_state) + + t = SearchTree(p) + + t.search() + + assert str(t.plan) == "[Stack(e,b), Unstack(c,d), Putdown(c), Pickup(d), Stack(d,c), Unstack(e,b), Stack(e,d), Pickup(a), Stack(a,e)]" \ No newline at end of file diff --git a/tree_search.py b/tree_search.py new file mode 100644 index 0000000..d616db9 --- /dev/null +++ b/tree_search.py @@ -0,0 +1,115 @@ + +# Module: tree_search +# +# 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): + self.state = state + self.parent = parent + def __str__(self): + return "no(" + str(self.state) + "," + str(self.parent) + ")" + def __repr__(self): + return str(self) + +# Arvores de pesquisa +class SearchTree: + + # construtor + def __init__(self,problem, strategy='breadth'): + self.problem = problem + root = SearchNode(problem.initial, None) + self.open_nodes = [root] + self.strategy = strategy + self.solution = None + + # 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): + while self.open_nodes != []: + node = self.open_nodes.pop(0) + if self.problem.goal_test(node.state): + self.solution = node + return self.get_path(node) + lnewnodes = [] + for a in self.problem.domain.actions(node.state): + newstate = self.problem.domain.result(node.state,a) + newnode = SearchNode(newstate,node) + lnewnodes.append(newnode) + self.add_to_open(lnewnodes) + 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': + pass +