Rework of chapters 2 and 3

This commit is contained in:
Christian Risi
2025-08-19 20:03:51 +02:00
parent 1a3e33b5ed
commit 642559ee4d
3 changed files with 618 additions and 51 deletions

View File

@@ -49,32 +49,37 @@ This is an `agent` which has `factored` or `structures` representation of states
## Search Problem
A search problem is the union of the followings:
A search problem is the union of the followings:
- **State Space**
Set of *possible* `states`.
Set of *possible* `states`.
It can be represented as a `graph` where each `state` is a `node`
and each `action` is an `edge`, leading from a `state` to another
and each `action` is an `edge`, leading from a `state` to another
- **Initial State**
The initial `state` the `agent` is in
- **Goal State(s)**
The `state` where the `agent` will have reached its goal. There can be multiple
The `state` where the `agent` will have reached its goal. There can be multiple
`goal-states`
- **Available Actions**
All the `actions` available to the `agent`:
```python
def get_actions(state: State) : set[Action]
```
- **Transition Model**
A `function` which returns the `next-state` after taking
an `action` in the `current-state`:
```python
def move_to_next_state(state: State, action: Action): State
```
- **Action Cost Function**
A `function` which denotes the cost of taking that
A `function` which denotes the cost of taking that
`action` to reach a `new-state` from `current-state`:
```python
def action_cost(
current_state: State,
@@ -86,28 +91,28 @@ A search problem is the union of the followings:
A `sequence` of `actions` to go from a `state` to another is called `path`.
A `path` leading to the `goal` is called a `solution`.
The ***shortest*** `path` to the `goal` is called the `optimal-solution`, or
The ***shortest*** `path` to the `goal` is called the `optimal-solution`, or
in other words, this is the `path` with the ***lowest*** `cost`.
Obviously we always need a level of ***abstraction*** to get our `agent`
perform at its best. For example, we don't need to express any detail
Obviously we always need a level of ***abstraction*** to get our `agent`
perform at its best. For example, we don't need to express any detail
about the ***physics*** of the real world to go from *point-A* to *point-B*.
## Searching Algorithms
Most algorithms used to solve [Searching Problems](#search-problem) rely
Most algorithms used to solve [Searching Problems](#search-problem) rely
on a `tree` based representation, where the `root-node` is the `initial-state`
and each `child-node` is the `next-available-state` from a `node`.
By the `data-structure` being a `search-tree`, each `node` has a ***unique***
`path` back to the `root` as each `node` has a ***reference*** to the `parent-node`.
For each `action` we generate a `node` and each `generated-node`, wheter
For each `action` we generate a `node` and each `generated-node`, wheter
***further explored*** or not, become part of the `frontier` or `fringe`.
> [!TIP]
> Before going on how to implement `search` algorithms,
> let's say that we'll use these `data-structures` for
> Before going on how to implement `search` algorithms,
> let's say that we'll use these `data-structures` for
> `frontiers`:
>
> - `priority-queue` when we need to evaluate for `lowest-costs` first
@@ -116,41 +121,45 @@ For each `action` we generate a `node` and each `generated-node`, wheter
>
> Then we need to take care of ***reduntant-paths*** in some ways:
>
> - Remember all previous `states` and only care for best `paths` to these
> - Remember all previous `states` and only care for best `paths` to these
> `states`, ***best when `problem` fits into memory***.
> - Ignore the problem when it is ***rare*** or ***impossible*** to repeat them,
> like in an ***assembly line*** in factories.
> - Check for repeated `states` along the `parent-chain` up to the `root` or
> - Check for repeated `states` along the `parent-chain` up to the `root` or
> first `n-links`. This allows us to ***save up on memory***
>
> If we check for `redundant-paths` we have a `graph-search`, otherwise a `tree-like-search`
>
>
### Measuring Performance
We have 4 parameters:
- #### Completeness:
Is the `algorithm` guaranteed to find the `solution`, if any, and report
- #### Completeness
Is the `algorithm` guaranteed to find the `solution`, if any, and report
for ***no solution***?
This is easy for `finite` `state-spaces` while we need a ***systematic***
algorithm for `infinite` ones, though it would be difficult reporting
algorithm for `infinite` ones, though it would be difficult reporting
for ***no solution*** as it is impossible to explore the ***whole `space`***.
- #### Cost Optimality:
- #### Cost Optimality
Can it find the `optimal-solution`?
- #### Time Complexity:
- #### Time Complexity
`O(n) time` performance
- #### Space Complexity:
`O(n) space` performance, explicit one (if the `graph` is ***explicit***) or
by mean of:
- #### Space Complexity
- `depth` of `actions` for an `optimal-solution`
- `max-number-of-actions` in **any** `path`
- `branching-factor` for a node
`O(n) space` performance, explicit one (if the `graph` is ***explicit***) or
by mean of:
- `depth` of `actions` for an `optimal-solution`
- `max-number-of-actions` in **any** `path`
- `branching-factor` for a node
### Uninformed Algorithms
@@ -238,19 +247,19 @@ These `algorithms` know **nothing** about the `space`
```
In this `algorithm` we use the ***depth*** of `nodes` as the `cost` to
In this `algorithm` we use the ***depth*** of `nodes` as the `cost` to
reach such nodes.
In comparison to the [Best-First Search](#best-first-search), we have these
In comparison to the [Best-First Search](#best-first-search), we have these
differences:
- `FIFO Queue` instead of a `Priority Queue`:
Since we expand on ***breadth***, a `FIFO` guarantees us that
Since we expand on ***breadth***, a `FIFO` guarantees us that
all nodes are in order as the `nodes` generated at the same `depth`
are generated before that those at `depth + 1`.
- `early-goal test` instead of a `late-goal test`:
We can immediately see if the `state` is the `goal-state` as
We can immediately see if the `state` is the `goal-state` as
it would have the ***minimum `cost` already***
- The `reached_states` is now a `set` instead of a `dict`:
@@ -258,11 +267,12 @@ differences:
that we alread reached the ***minimum `cost`*** for that `state`
after the first time we reached it.
However the `space-complexity` and `time-complexity` are
***high*** with $O(b^d)$ space, where $b$ is the
However the `space-complexity` and `time-complexity` are
***high*** with $O(b^d)$ space, where $b$ is the
`max-branching-factor` and $d$ is the `search-depth`[^breadth-first-performance]
This algorithm is:
- `optimal`
- `complete` (as long each `action` has the same `cost`)
@@ -274,23 +284,24 @@ This algorithm is:
This algorithm is basically [Best-First Search](#best-first-search) but with `path_cost()`
as the `cost_function`.
It works by `expanding` all `nodes` that have the ***lowest*** `path-cost` and
evaluating them for the `goal` after `poppoing` them out of the `queue`, otherwise
it would pick up one of the `non-optimal solutions`.
It works by `expanding` all `nodes` that have the ***lowest*** `path-cost` and
evaluating them for the `goal` after `poppoing` them out of the `queue`, otherwise
it would pick up one of the `non-optimal solutions`.
Its ***performance*** depends on $C^{*}$, the `optimal-solution` and $\epsilon > 0$, the lower
bound over the `cost` of each `action`. The `worst-case` would be
bound over the `cost` of each `action`. The `worst-case` would be
$O(b^{1 + \frac{C^*}{\epsilon}})$ for bot `time` and `space-complexity`
In the `worst-case` the `complexity` is $O(b^{d + 1})$ when all `actions` cost $\epsilon$
This algorithm is:
- `optimal`
- `complete`
> [!TIP]
> Notice that at ***worst***, we will have to expand $\frac{C^*}{\epsilon}$ if ***each***
> action costed at most $\epsilon$, since $C^*$ is the `optimal-cost`, plus the
> action costed at most $\epsilon$, since $C^*$ is the `optimal-cost`, plus the
> ***last-expansion*** before realizing it got the `optimal-solution`
#### Depth-First Search
@@ -371,10 +382,11 @@ This algorithm is:
```
This is basically a [Best-First Search](#best-first-search) but with the `cost_function`
being the ***negative*** of `depth`. However we can use a `LIFO Queue`, instead of a
being the ***negative*** of `depth`. However we can use a `LIFO Queue`, instead of a
`cost_function`, and delete the `reached_space` `dict`.
This algorithm is:
- `non-optimal` as it returns the ***first*** `solution`, not the ***best***
- `incomplete` as it is `non-systematic`, but it is `complete` for `acyclic graphs`
and `trees`
@@ -479,12 +491,13 @@ One evolution of this algorithm, is the ***backtracking search***
This is a ***flavour*** of [Depth-First Search](#depth-first-search).
One addition of this algorithm is the parameter of `max_depth`. After we go after
One addition of this algorithm is the parameter of `max_depth`. After we go after
`max_depth`, we don't expand anymore.
This algorithm is:
- `non-optimal` (see [Depth-First Search](#depth-first-search))
- `incomplete` (see [Depth-First Search](#depth-first-search)) and it now depends
- `incomplete` (see [Depth-First Search](#depth-first-search)) and it now depends
on the `max_depth` as well
- $O(b^{max\_depth})$ `time-complexity`
- $O(b\, max\_depth)$ `space-complexity`
@@ -493,8 +506,8 @@ This algorithm is:
> This algorithm needs a way to handle `cycles` as its ***parent***
> [!TIP]
> Depending on the domain of the `problem`, we can estimate a good `max_depth`, for
> example, ***graphs*** have a number called `diameter` that tells us the
> Depending on the domain of the `problem`, we can estimate a good `max_depth`, for
> example, ***graphs*** have a number called `diameter` that tells us the
> ***max number of `actions` to reach any `node` in the `graph`***
#### Iterative Deepening Search
@@ -531,15 +544,16 @@ This is a ***flavour*** of `depth-limited search`. whenever it reaches the `max_
the `search` is ***restarted*** until one is found.
This algorithm is:
- `non-optimal` (see [Depth-First Search](#depth-first-search))
- `incomplete` (see [Depth-First Search](#depth-first-search)) and it now depends
- `incomplete` (see [Depth-First Search](#depth-first-search)) and it now depends
on the `max_depth` as well
- $O(b^{m})$ `time-complexity`
- $O(b\, m)$ `space-complexity`
> [!TIP]
> This is the ***preferred*** method for `uninformed-search` when
> ***we have no idea of the `max_depth`*** and ***the `space` is larger than memory***
> This is the ***preferred*** method for `uninformed-search` when
> ***we have no idea of the `max_depth`*** and ***the `space` is larger than memory***
#### Bidirectional Search
@@ -663,9 +677,10 @@ a [Best-First Search](#best-first-search), making us save up on ***memory*** and
On the other hand, the implementation algorithm is ***harder*** to implement
This algorithm is:
- `optimal` (see [Best-First Search](#best-first-search))
- `complete` (see [Best-First Search](#best-first-search))
- $O(b^{1 + \frac{C^*}{2\epsilon}})$ `time-complexity`
- $O(b^{1 + \frac{C^*}{2\epsilon}})$ `time-complexity`
(see [Uniform-Cost Search](#dijkstraalgorithm--aka-uniform-cost-search))
- $O(b^{1 + \frac{C^*}{2\epsilon}})$ `space-complexity`
(see [Uniform-Cost Search](#dijkstraalgorithm--aka-uniform-cost-search))
@@ -676,7 +691,7 @@ This algorithm is:
>
> - $C^*$ is the `optimal-path` and no `node` with $path\_cost > \frac{C^*}{2}$ will be expanded
>
> This is an important speedup, however, without having a `bi-directional` `cost_function`,
> This is an important speedup, however, without having a `bi-directional` `cost_function`,
> then we need to check for the ***best `solution`*** several times.
### Informed Algorithms | AKA Heuristic Search
@@ -766,8 +781,8 @@ These `algorithms` know something about the ***closeness*** of nodes
```
In a `best_first_search` we start from the `root` and then we
`expand` and add these `states` as `nodes` at `frontier` if they are
In a `best_first_search` we start from the `root` and then we
`expand` and add these `states` as `nodes` at `frontier` if they are
***either new or at lower path_cost***.
Whenever we get from a `state` to a `node`, we keep track of:
@@ -792,4 +807,4 @@ the `goal-state`, then the solution is `null`.
Ch. 3 pg. 95
[^dijkstra-algorithm]: Artificial Intelligence: A Modern Approach Global Edition 4th |
Ch. 3 pg. 96
Ch. 3 pg. 96