cosc604 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cosc604-0.1.0/LICENSE +21 -0
- cosc604-0.1.0/PKG-INFO +93 -0
- cosc604-0.1.0/README.md +71 -0
- cosc604-0.1.0/pyproject.toml +33 -0
- cosc604-0.1.0/setup.cfg +4 -0
- cosc604-0.1.0/src/cosc604/__init__.py +22 -0
- cosc604-0.1.0/src/cosc604/graph_problem.py +69 -0
- cosc604-0.1.0/src/cosc604/priority_queue_demo.py +62 -0
- cosc604-0.1.0/src/cosc604/puzzle_problem.py +66 -0
- cosc604-0.1.0/src/cosc604/searches.py +142 -0
- cosc604-0.1.0/src/cosc604.egg-info/PKG-INFO +93 -0
- cosc604-0.1.0/src/cosc604.egg-info/SOURCES.txt +13 -0
- cosc604-0.1.0/src/cosc604.egg-info/dependency_links.txt +1 -0
- cosc604-0.1.0/src/cosc604.egg-info/requires.txt +5 -0
- cosc604-0.1.0/src/cosc604.egg-info/top_level.txt +1 -0
cosc604-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019-2026 Andreas Henschel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cosc604-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cosc604
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Teaching implementations of search algorithms (BFS, DFS, UCS, A*) from Russell & Norvig's Artificial Intelligence: A Modern Approach, developed for COSC604.
|
|
5
|
+
Author-email: Andreas Henschel <andreas.henschel@ku.ac.ae>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: artificial-intelligence,search,aima,education,russell-norvig
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Topic :: Education
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: networkx
|
|
19
|
+
Provides-Extra: plot
|
|
20
|
+
Requires-Dist: matplotlib; extra == "plot"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# cosc604
|
|
24
|
+
|
|
25
|
+
Teaching code for **COSC604 – Techniques in Artificial Intelligence**,
|
|
26
|
+
developed alongside Assignment 1 (instructor: Andreas Henschel,
|
|
27
|
+
andreas.henschel@ku.ac.ae). It implements the generic search infrastructure
|
|
28
|
+
from Russell & Norvig, *Artificial Intelligence: A Modern Approach* (Ch. 3):
|
|
29
|
+
fringes/queues, the search-tree `Node`, `graph_search`/`tree_search`, and the
|
|
30
|
+
standard strategies built on top of them (BFS, DFS, uniform-cost, A*).
|
|
31
|
+
|
|
32
|
+
`Assignment1.ipynb` walks through the same code interactively, with the
|
|
33
|
+
routing-problem and tile-puzzle exercises described there. The modules under
|
|
34
|
+
`src/cosc604/` are the packaged, importable version of that code.
|
|
35
|
+
|
|
36
|
+
## Package layout
|
|
37
|
+
|
|
38
|
+
| Module | Contents |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `cosc604.searches` | `Fringe`, `FIFO`, `LIFO`, `PriorityQueue`, `Node`, `graph_search`, `tree_search`, and the convenience wrappers `breadth_first_graph_search`, `depth_first_graph_search`, `depth_first_tree_search`, `astar_graph_search`, `uniform_cost_search`. |
|
|
41
|
+
| `cosc604.priority_queue_demo` | Standalone examples of how `PriorityQueue`'s priority function `f` shapes ordering (digit-sum, "VIP title" count). Run with `python -m cosc604.priority_queue_demo`. |
|
|
42
|
+
| `cosc604.graph_problem` | `GraphProblem` — the routing problem from the lecture slides, built on `networkx`. Includes the toy example and the Romania map. |
|
|
43
|
+
| `cosc604.puzzle_problem` | `PuzzleProblem` / `PuzzleState` — the sliding tile puzzle (8-puzzle for `size=3`). `PuzzleState.successors`, `__hash__` and `__eq__` are left as an exercise — implement them to make the puzzle searchable. |
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
From PyPI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install cosc604
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or, from this directory, as an editable install:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install -e .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This pulls in `numpy` and `networkx`. To also plot the Romania graph
|
|
60
|
+
(`graph_problem.draw_romania()`), install the optional `plot` extra:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install "cosc604[plot]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quickstart
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from cosc604 import GraphProblem, uniform_cost_search
|
|
70
|
+
|
|
71
|
+
connections = [('S', 'A', 5), ('S', 'B', 3), ('S', 'C', 1),
|
|
72
|
+
('A', 'G', 1), ('B', 'G', 2), ('C', 'G', 17)]
|
|
73
|
+
toy = GraphProblem('S', 'G', connections, directed=True)
|
|
74
|
+
|
|
75
|
+
solution = uniform_cost_search(toy)
|
|
76
|
+
print([(node.state, node.action) for node in solution.getPath()])
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Assignment
|
|
80
|
+
|
|
81
|
+
Assignment 1 asks you to:
|
|
82
|
+
|
|
83
|
+
1. Add a `LIFO` fringe and complete the generic search algorithms (both done
|
|
84
|
+
here) — read through `searches.py` to understand how `graph_search` and
|
|
85
|
+
`tree_search` use a `Fringe` to implement each strategy.
|
|
86
|
+
2. Implement `PuzzleState.successors`, `__hash__` and `__eq__` in
|
|
87
|
+
`puzzle_problem.py` so `PuzzleProblem` can be solved with the same search
|
|
88
|
+
algorithms used for the routing problem, and reproduce the tile-puzzle
|
|
89
|
+
results shown in the slides (`tileslides.png`).
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT — see [LICENSE](LICENSE).
|
cosc604-0.1.0/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# cosc604
|
|
2
|
+
|
|
3
|
+
Teaching code for **COSC604 – Techniques in Artificial Intelligence**,
|
|
4
|
+
developed alongside Assignment 1 (instructor: Andreas Henschel,
|
|
5
|
+
andreas.henschel@ku.ac.ae). It implements the generic search infrastructure
|
|
6
|
+
from Russell & Norvig, *Artificial Intelligence: A Modern Approach* (Ch. 3):
|
|
7
|
+
fringes/queues, the search-tree `Node`, `graph_search`/`tree_search`, and the
|
|
8
|
+
standard strategies built on top of them (BFS, DFS, uniform-cost, A*).
|
|
9
|
+
|
|
10
|
+
`Assignment1.ipynb` walks through the same code interactively, with the
|
|
11
|
+
routing-problem and tile-puzzle exercises described there. The modules under
|
|
12
|
+
`src/cosc604/` are the packaged, importable version of that code.
|
|
13
|
+
|
|
14
|
+
## Package layout
|
|
15
|
+
|
|
16
|
+
| Module | Contents |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `cosc604.searches` | `Fringe`, `FIFO`, `LIFO`, `PriorityQueue`, `Node`, `graph_search`, `tree_search`, and the convenience wrappers `breadth_first_graph_search`, `depth_first_graph_search`, `depth_first_tree_search`, `astar_graph_search`, `uniform_cost_search`. |
|
|
19
|
+
| `cosc604.priority_queue_demo` | Standalone examples of how `PriorityQueue`'s priority function `f` shapes ordering (digit-sum, "VIP title" count). Run with `python -m cosc604.priority_queue_demo`. |
|
|
20
|
+
| `cosc604.graph_problem` | `GraphProblem` — the routing problem from the lecture slides, built on `networkx`. Includes the toy example and the Romania map. |
|
|
21
|
+
| `cosc604.puzzle_problem` | `PuzzleProblem` / `PuzzleState` — the sliding tile puzzle (8-puzzle for `size=3`). `PuzzleState.successors`, `__hash__` and `__eq__` are left as an exercise — implement them to make the puzzle searchable. |
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
From PyPI:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install cosc604
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or, from this directory, as an editable install:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This pulls in `numpy` and `networkx`. To also plot the Romania graph
|
|
38
|
+
(`graph_problem.draw_romania()`), install the optional `plot` extra:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install "cosc604[plot]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from cosc604 import GraphProblem, uniform_cost_search
|
|
48
|
+
|
|
49
|
+
connections = [('S', 'A', 5), ('S', 'B', 3), ('S', 'C', 1),
|
|
50
|
+
('A', 'G', 1), ('B', 'G', 2), ('C', 'G', 17)]
|
|
51
|
+
toy = GraphProblem('S', 'G', connections, directed=True)
|
|
52
|
+
|
|
53
|
+
solution = uniform_cost_search(toy)
|
|
54
|
+
print([(node.state, node.action) for node in solution.getPath()])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Assignment
|
|
58
|
+
|
|
59
|
+
Assignment 1 asks you to:
|
|
60
|
+
|
|
61
|
+
1. Add a `LIFO` fringe and complete the generic search algorithms (both done
|
|
62
|
+
here) — read through `searches.py` to understand how `graph_search` and
|
|
63
|
+
`tree_search` use a `Fringe` to implement each strategy.
|
|
64
|
+
2. Implement `PuzzleState.successors`, `__hash__` and `__eq__` in
|
|
65
|
+
`puzzle_problem.py` so `PuzzleProblem` can be solved with the same search
|
|
66
|
+
algorithms used for the routing problem, and reproduce the tile-puzzle
|
|
67
|
+
results shown in the slides (`tileslides.png`).
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cosc604"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Teaching implementations of search algorithms (BFS, DFS, UCS, A*) from Russell & Norvig's Artificial Intelligence: A Modern Approach, developed for COSC604."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Andreas Henschel", email = "andreas.henschel@ku.ac.ae" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["artificial-intelligence", "search", "aima", "education", "russell-norvig"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Education",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
"Topic :: Education",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"numpy",
|
|
26
|
+
"networkx",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
plot = ["matplotlib"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
cosc604-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""cosc604: teaching implementations of the search algorithms from
|
|
2
|
+
Russell & Norvig, *Artificial Intelligence: A Modern Approach* (Ch. 3),
|
|
3
|
+
developed for COSC604 - Techniques in Artificial Intelligence.
|
|
4
|
+
"""
|
|
5
|
+
from .searches import (
|
|
6
|
+
Fringe, FIFO, LIFO, PriorityQueue, Node,
|
|
7
|
+
graph_search, tree_search,
|
|
8
|
+
breadth_first_graph_search, depth_first_graph_search,
|
|
9
|
+
depth_first_tree_search, astar_graph_search, uniform_cost_search,
|
|
10
|
+
)
|
|
11
|
+
from .graph_problem import GraphProblem
|
|
12
|
+
from .puzzle_problem import PuzzleProblem, PuzzleState
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Fringe", "FIFO", "LIFO", "PriorityQueue", "Node",
|
|
18
|
+
"graph_search", "tree_search",
|
|
19
|
+
"breadth_first_graph_search", "depth_first_graph_search",
|
|
20
|
+
"depth_first_tree_search", "astar_graph_search", "uniform_cost_search",
|
|
21
|
+
"GraphProblem", "PuzzleProblem", "PuzzleState",
|
|
22
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""The Routing problem from the textbook: states are cities, actions move
|
|
2
|
+
along weighted edges of a graph. GraphProblem plugs into the generic
|
|
3
|
+
searches.py algorithms via the successors/goal_test interface.
|
|
4
|
+
|
|
5
|
+
Run directly with: python -m cosc604.graph_problem
|
|
6
|
+
"""
|
|
7
|
+
import networkx as nx
|
|
8
|
+
|
|
9
|
+
from .searches import uniform_cost_search
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GraphProblem:
|
|
13
|
+
def __init__(self, initial, goal, connections, locations=None, directed=False):
|
|
14
|
+
self.initial = initial
|
|
15
|
+
self.goal = goal
|
|
16
|
+
self.locations = locations
|
|
17
|
+
self.graph = nx.DiGraph() if directed else nx.Graph()
|
|
18
|
+
for cityA, cityB, distance in connections:
|
|
19
|
+
self.graph.add_edge(cityA, cityB, cost=distance)
|
|
20
|
+
|
|
21
|
+
def successors(self, state):
|
|
22
|
+
# Exactly as defined in Lecture slides
|
|
23
|
+
return [("go to %s" % city, connection['cost'], city)
|
|
24
|
+
for city, connection in self.graph[state].items()]
|
|
25
|
+
|
|
26
|
+
def goal_test(self, state):
|
|
27
|
+
return state == self.goal
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Warm up: the little toy example from the slides
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
toyConnections = [('S', 'A', 5), ('S', 'B', 3), ('S', 'C', 1),
|
|
35
|
+
('A', 'G', 1), ('B', 'G', 2), ('C', 'G', 17)]
|
|
36
|
+
toy = GraphProblem('S', 'G', toyConnections, directed=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Romania: the classic bigger example. Try different initial/goal states.
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
connections = [('A', 'S', 140), ('A', 'Z', 75), ('A', 'T', 118), ('C', 'P', 138), ('C', 'R', 146), ('C', 'D', 120),
|
|
44
|
+
('B', 'P', 101), ('B', 'U', 85), ('B', 'G', 90), ('B', 'F', 211), ('E', 'H', 86), ('D', 'M', 75),
|
|
45
|
+
('F', 'S', 99), ('I', 'V', 92), ('I', 'N', 87), ('H', 'U', 98), ('L', 'M', 70), ('L', 'T', 111),
|
|
46
|
+
('O', 'S', 151), ('O', 'Z', 71), ('P', 'R', 97), ('R', 'S', 80), ('U', 'V', 142)]
|
|
47
|
+
|
|
48
|
+
locations = {'A': (91, 492), 'C': (253, 288), 'B': (400, 327), 'E': (562, 293), 'D': (165, 299), 'G': (375, 270), 'F': (305, 449),
|
|
49
|
+
'I': (473, 506), 'H': (534, 350), 'M': (168, 339), 'L': (165, 379), 'O': (131, 571), 'N': (406, 537), 'P': (320, 368),
|
|
50
|
+
'S': (207, 457), 'R': (233, 410), 'U': (456, 350), 'T': (94, 410), 'V': (509, 444), 'Z': (108, 531)}
|
|
51
|
+
|
|
52
|
+
romania = GraphProblem('A', 'B', connections, locations=locations)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def draw_romania():
|
|
56
|
+
"""Plot the Romania graph (needs matplotlib; run inside a notebook or
|
|
57
|
+
with an interactive backend to actually see the figure)."""
|
|
58
|
+
import pylab
|
|
59
|
+
pylab.clf()
|
|
60
|
+
nx.draw(romania.graph, romania.locations, with_labels=True)
|
|
61
|
+
pylab.show()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
print("Toy successors of C:", toy.successors('C'))
|
|
66
|
+
|
|
67
|
+
sol = uniform_cost_search(toy)
|
|
68
|
+
path = [(node.state, node.action) for node in sol.getPath()]
|
|
69
|
+
print("Uniform-cost solution S -> G:", path)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Standalone examples of how searches.PriorityQueue orders items.
|
|
2
|
+
|
|
3
|
+
The PriorityQueue is a Fringe that takes a function `f` at construction time.
|
|
4
|
+
`f` maps an item (later: a search Node) to a priority; the queue always pops
|
|
5
|
+
the item with the lowest priority first. These two examples use plain
|
|
6
|
+
integers and strings (not Nodes) so you can see the ordering mechanics in
|
|
7
|
+
isolation, before PriorityQueue gets reused as the fringe for
|
|
8
|
+
uniform-cost / A* search in searches.py.
|
|
9
|
+
|
|
10
|
+
Run directly with: python -m cosc604.priority_queue_demo
|
|
11
|
+
"""
|
|
12
|
+
from .searches import PriorityQueue
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def digitSum(nr):
|
|
16
|
+
"""Priority = sum of the item's digits (smaller sum -> popped first)."""
|
|
17
|
+
ds = 0
|
|
18
|
+
while nr > 0:
|
|
19
|
+
ds += nr % 10
|
|
20
|
+
nr = nr // 10
|
|
21
|
+
return ds
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def countImportance(title):
|
|
25
|
+
"""The importance is the number of V's in the title, with more V's being
|
|
26
|
+
more important. The negation is used so that the priority queue will
|
|
27
|
+
order elements with more V's first, since the priority queue orders by
|
|
28
|
+
lowest value first."""
|
|
29
|
+
return -title.split()[0].count('V')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def demo_digit_sum():
|
|
33
|
+
print("=== PriorityQueue ordered by digit sum ===")
|
|
34
|
+
pq = PriorityQueue(digitSum)
|
|
35
|
+
|
|
36
|
+
# Example from class: the priority queue will order elements based on
|
|
37
|
+
# the sum of their digits
|
|
38
|
+
for element in [432, 52, 99, 12]:
|
|
39
|
+
pq.push(element)
|
|
40
|
+
|
|
41
|
+
while not pq.empty():
|
|
42
|
+
print(pq.pop())
|
|
43
|
+
print("Remaining PQ:", pq.fringe)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def demo_vip_titles():
|
|
47
|
+
print("=== PriorityQueue ordered by number of V's in the title ===")
|
|
48
|
+
pq = PriorityQueue(countImportance)
|
|
49
|
+
pq.push("Normal dude")
|
|
50
|
+
pq.push("VVVIP Elon Musk")
|
|
51
|
+
pq.push("Voldemort")
|
|
52
|
+
pq.push("VVIP Bill Gates")
|
|
53
|
+
|
|
54
|
+
while not pq.empty():
|
|
55
|
+
print(pq.pop())
|
|
56
|
+
print("Remaining PQ:", pq.fringe)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
demo_digit_sum()
|
|
61
|
+
print()
|
|
62
|
+
demo_vip_titles()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""The sliding tile puzzle problem (e.g. the 8-puzzle for size=3).
|
|
2
|
+
|
|
3
|
+
PuzzleState.successors/__hash__/__eq__ are left as stubs for you to
|
|
4
|
+
implement -- see the comments inside each method. Once PuzzleState is
|
|
5
|
+
complete, PuzzleProblem plugs straight into the generic search algorithms
|
|
6
|
+
in searches.py, the same way GraphProblem does.
|
|
7
|
+
"""
|
|
8
|
+
import random
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PuzzleState:
|
|
14
|
+
def __init__(self, matrix=None, goal=False, init=False, size=3):
|
|
15
|
+
self.size = size
|
|
16
|
+
if not matrix is None: # 0 represents empty spot
|
|
17
|
+
self.matrix = matrix
|
|
18
|
+
else: # defining init or goal state
|
|
19
|
+
permutation = np.array(range(size * size))
|
|
20
|
+
if init:
|
|
21
|
+
random.shuffle(permutation)
|
|
22
|
+
self.matrix = permutation.reshape((size, size))
|
|
23
|
+
|
|
24
|
+
def successors(self):
|
|
25
|
+
pass
|
|
26
|
+
# return a list of successors
|
|
27
|
+
# each successor is a tuple of
|
|
28
|
+
# 1. a string, describing the action,
|
|
29
|
+
# 2. action cost (here 1),
|
|
30
|
+
# 3. and the new state
|
|
31
|
+
|
|
32
|
+
def __hash__(self):
|
|
33
|
+
pass
|
|
34
|
+
# return a hash code, if you have a matrix, you can uncomment this:
|
|
35
|
+
# return hash(tuple(self.matrix.flatten()))
|
|
36
|
+
|
|
37
|
+
def __eq__(self, other):
|
|
38
|
+
pass
|
|
39
|
+
# this function defines equality between objects, if you have a matrix, you can uncomment this:
|
|
40
|
+
# return np.alltrue(other.matrix == self.matrix)
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return str(self.matrix)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PuzzleProblem:
|
|
47
|
+
def __init__(self, size=3): # size 3 means 3x3 field
|
|
48
|
+
self.size = size
|
|
49
|
+
self.initial = PuzzleState(init=True, size=size) # init state is shuffled
|
|
50
|
+
self.goal = PuzzleState(goal=True, size=size) # goal state is unshuffled
|
|
51
|
+
|
|
52
|
+
def successors(self, state):
|
|
53
|
+
# this assumes that PuzzleState does the actual job of finding the successors
|
|
54
|
+
return state.successors()
|
|
55
|
+
|
|
56
|
+
def goal_test(self, state):
|
|
57
|
+
return state == self.goal
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
puzzle = PuzzleProblem(size=3)
|
|
62
|
+
print("Initial state:\n", puzzle.initial)
|
|
63
|
+
print("Goal state:\n", puzzle.goal)
|
|
64
|
+
# Once PuzzleState is implemented, e.g.:
|
|
65
|
+
# from searches import breadth_first_graph_search
|
|
66
|
+
# sol = breadth_first_graph_search(puzzle)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Generic search infrastructure: fringes (queues), the Node class, and the
|
|
2
|
+
graph/tree search algorithms, plus convenience wrappers for the standard
|
|
3
|
+
search strategies (BFS, DFS, uniform-cost, A*).
|
|
4
|
+
|
|
5
|
+
This module is problem-agnostic: it only relies on a `problem` object that
|
|
6
|
+
exposes `initial`, `goal_test(state)` and `successors(state)`. See
|
|
7
|
+
graph_problem.py and puzzle_problem.py for concrete problems.
|
|
8
|
+
"""
|
|
9
|
+
import bisect
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Fringes (the frontier / open list used by search)
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
class Fringe:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.fringe = []
|
|
19
|
+
|
|
20
|
+
def empty(self):
|
|
21
|
+
return len(self.fringe) == 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FIFO(Fringe):
|
|
25
|
+
"""First-in-first-out queue -> breadth-first search."""
|
|
26
|
+
def push(self, item):
|
|
27
|
+
self.fringe.append(item)
|
|
28
|
+
|
|
29
|
+
def pop(self):
|
|
30
|
+
return self.fringe.pop(0)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LIFO(Fringe):
|
|
34
|
+
"""Last-in-first-out queue (a stack) -> depth-first search."""
|
|
35
|
+
def push(self, item):
|
|
36
|
+
self.fringe.append(item)
|
|
37
|
+
|
|
38
|
+
def pop(self):
|
|
39
|
+
return self.fringe.pop()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PriorityQueue(Fringe):
|
|
43
|
+
"""Orders items by ascending priority, where priority is computed by
|
|
44
|
+
the function `f` passed at construction time. See priority_queue_demo.py
|
|
45
|
+
for standalone examples of how `f` shapes the ordering."""
|
|
46
|
+
def __init__(self, f):
|
|
47
|
+
self.f = f
|
|
48
|
+
super().__init__()
|
|
49
|
+
|
|
50
|
+
def push(self, item):
|
|
51
|
+
# apply the function that provides the priority for the current item
|
|
52
|
+
# later, items will be nodes, and priorities will be backward-, forward cost or both
|
|
53
|
+
priority = self.f(item)
|
|
54
|
+
bisect.insort(self.fringe, (priority, item))
|
|
55
|
+
|
|
56
|
+
def pop(self):
|
|
57
|
+
return self.fringe.pop(0)[1]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Search tree node
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
class Node:
|
|
65
|
+
def __init__(self, state=None, parent=None, action=None, path_cost=0):
|
|
66
|
+
self.state = state
|
|
67
|
+
self.parent = parent
|
|
68
|
+
self.action = action
|
|
69
|
+
self.path_cost = path_cost
|
|
70
|
+
|
|
71
|
+
def getPath(self):
|
|
72
|
+
"""getting the path of parents up to the root"""
|
|
73
|
+
currentNode = self
|
|
74
|
+
path = [self]
|
|
75
|
+
while currentNode.parent: # stops when parent is None, ie root
|
|
76
|
+
path.append(currentNode.parent)
|
|
77
|
+
currentNode = currentNode.parent
|
|
78
|
+
path.reverse() # from root to this node
|
|
79
|
+
return path
|
|
80
|
+
|
|
81
|
+
def expand(self, problem):
|
|
82
|
+
successors = problem.successors(self.state)
|
|
83
|
+
return [Node(newState, self, action, self.path_cost + cost)
|
|
84
|
+
for (action, cost, newState) in successors]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Search algorithms
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def graph_search(problem, fringe):
|
|
92
|
+
"""Search through the successors of a problem to find a goal.
|
|
93
|
+
The argument fringe should be an empty queue.
|
|
94
|
+
If two paths reach a state, only use the best one. [Fig. 3.18]"""
|
|
95
|
+
closed = set() # can store hashable objects, thats why we need to define a hash code for states
|
|
96
|
+
fringe.push(Node(problem.initial))
|
|
97
|
+
while not fringe.empty():
|
|
98
|
+
node = fringe.pop()
|
|
99
|
+
if problem.goal_test(node.state):
|
|
100
|
+
return node
|
|
101
|
+
if node.state not in closed:
|
|
102
|
+
closed.add(node.state)
|
|
103
|
+
successors = node.expand(problem)
|
|
104
|
+
for snode in successors:
|
|
105
|
+
fringe.push(snode)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def tree_search(problem, fringe):
|
|
109
|
+
"""Search through the successors of a problem to find a goal.
|
|
110
|
+
The argument fringe should be an empty queue."""
|
|
111
|
+
fringe.push(Node(problem.initial))
|
|
112
|
+
while not fringe.empty():
|
|
113
|
+
node = fringe.pop()
|
|
114
|
+
if problem.goal_test(node.state):
|
|
115
|
+
return node
|
|
116
|
+
successors = node.expand(problem)
|
|
117
|
+
for snode in successors:
|
|
118
|
+
fringe.push(snode)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Convenience functions for the standard search strategies
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
def breadth_first_graph_search(problem):
|
|
126
|
+
return graph_search(problem, FIFO())
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def depth_first_graph_search(problem):
|
|
130
|
+
return graph_search(problem, LIFO())
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def depth_first_tree_search(problem):
|
|
134
|
+
return tree_search(problem, LIFO())
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def astar_graph_search(problem, f):
|
|
138
|
+
return graph_search(problem, PriorityQueue(f))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def uniform_cost_search(problem):
|
|
142
|
+
return graph_search(problem, PriorityQueue(lambda node: node.path_cost))
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cosc604
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Teaching implementations of search algorithms (BFS, DFS, UCS, A*) from Russell & Norvig's Artificial Intelligence: A Modern Approach, developed for COSC604.
|
|
5
|
+
Author-email: Andreas Henschel <andreas.henschel@ku.ac.ae>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: artificial-intelligence,search,aima,education,russell-norvig
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Topic :: Education
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: networkx
|
|
19
|
+
Provides-Extra: plot
|
|
20
|
+
Requires-Dist: matplotlib; extra == "plot"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# cosc604
|
|
24
|
+
|
|
25
|
+
Teaching code for **COSC604 – Techniques in Artificial Intelligence**,
|
|
26
|
+
developed alongside Assignment 1 (instructor: Andreas Henschel,
|
|
27
|
+
andreas.henschel@ku.ac.ae). It implements the generic search infrastructure
|
|
28
|
+
from Russell & Norvig, *Artificial Intelligence: A Modern Approach* (Ch. 3):
|
|
29
|
+
fringes/queues, the search-tree `Node`, `graph_search`/`tree_search`, and the
|
|
30
|
+
standard strategies built on top of them (BFS, DFS, uniform-cost, A*).
|
|
31
|
+
|
|
32
|
+
`Assignment1.ipynb` walks through the same code interactively, with the
|
|
33
|
+
routing-problem and tile-puzzle exercises described there. The modules under
|
|
34
|
+
`src/cosc604/` are the packaged, importable version of that code.
|
|
35
|
+
|
|
36
|
+
## Package layout
|
|
37
|
+
|
|
38
|
+
| Module | Contents |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `cosc604.searches` | `Fringe`, `FIFO`, `LIFO`, `PriorityQueue`, `Node`, `graph_search`, `tree_search`, and the convenience wrappers `breadth_first_graph_search`, `depth_first_graph_search`, `depth_first_tree_search`, `astar_graph_search`, `uniform_cost_search`. |
|
|
41
|
+
| `cosc604.priority_queue_demo` | Standalone examples of how `PriorityQueue`'s priority function `f` shapes ordering (digit-sum, "VIP title" count). Run with `python -m cosc604.priority_queue_demo`. |
|
|
42
|
+
| `cosc604.graph_problem` | `GraphProblem` — the routing problem from the lecture slides, built on `networkx`. Includes the toy example and the Romania map. |
|
|
43
|
+
| `cosc604.puzzle_problem` | `PuzzleProblem` / `PuzzleState` — the sliding tile puzzle (8-puzzle for `size=3`). `PuzzleState.successors`, `__hash__` and `__eq__` are left as an exercise — implement them to make the puzzle searchable. |
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
From PyPI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install cosc604
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or, from this directory, as an editable install:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install -e .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This pulls in `numpy` and `networkx`. To also plot the Romania graph
|
|
60
|
+
(`graph_problem.draw_romania()`), install the optional `plot` extra:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install "cosc604[plot]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quickstart
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from cosc604 import GraphProblem, uniform_cost_search
|
|
70
|
+
|
|
71
|
+
connections = [('S', 'A', 5), ('S', 'B', 3), ('S', 'C', 1),
|
|
72
|
+
('A', 'G', 1), ('B', 'G', 2), ('C', 'G', 17)]
|
|
73
|
+
toy = GraphProblem('S', 'G', connections, directed=True)
|
|
74
|
+
|
|
75
|
+
solution = uniform_cost_search(toy)
|
|
76
|
+
print([(node.state, node.action) for node in solution.getPath()])
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Assignment
|
|
80
|
+
|
|
81
|
+
Assignment 1 asks you to:
|
|
82
|
+
|
|
83
|
+
1. Add a `LIFO` fringe and complete the generic search algorithms (both done
|
|
84
|
+
here) — read through `searches.py` to understand how `graph_search` and
|
|
85
|
+
`tree_search` use a `Fringe` to implement each strategy.
|
|
86
|
+
2. Implement `PuzzleState.successors`, `__hash__` and `__eq__` in
|
|
87
|
+
`puzzle_problem.py` so `PuzzleProblem` can be solved with the same search
|
|
88
|
+
algorithms used for the routing problem, and reproduce the tile-puzzle
|
|
89
|
+
results shown in the slides (`tileslides.png`).
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/cosc604/__init__.py
|
|
5
|
+
src/cosc604/graph_problem.py
|
|
6
|
+
src/cosc604/priority_queue_demo.py
|
|
7
|
+
src/cosc604/puzzle_problem.py
|
|
8
|
+
src/cosc604/searches.py
|
|
9
|
+
src/cosc604.egg-info/PKG-INFO
|
|
10
|
+
src/cosc604.egg-info/SOURCES.txt
|
|
11
|
+
src/cosc604.egg-info/dependency_links.txt
|
|
12
|
+
src/cosc604.egg-info/requires.txt
|
|
13
|
+
src/cosc604.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cosc604
|