Metadata-Version: 2.4
Name: pathos-ai
Version: 0.2.0
Summary: Production-ready classical AI search algorithms — declare your problem, the solver picks the algorithm. Includes an anytime A* cascade by default.
Project-URL: Homepage, https://uhgia.org/pathos/
Project-URL: Documentation, https://uhgia.org/pathos/
Project-URL: Repository, https://github.com/gia-uh/pathos
Project-URL: Issues, https://github.com/gia-uh/pathos/issues
Project-URL: Changelog, https://github.com/gia-uh/pathos/releases
Author-email: Alejandro Piad Morffis <apiad@apiad.net>
Maintainer-email: Alejandro Piad Morffis <apiad@apiad.net>
License: MIT License
        
        Copyright (c) 2026 gia-uh
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in
        all copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
        THE SOFTWARE.
License-File: LICENSE
Keywords: a-star,ai,anytime,constraint-satisfaction,csp,genetic-algorithm,graph-search,heuristic,mcts,metaheuristics,minimax,optimization,particle-swarm,planning,search,simulated-annealing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: mkdocs-material>=9.5; extra == 'dev'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'dev'
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Description-Content-Type: text/markdown

# PATHOS — Python AI Search Library

[![CI](https://github.com/gia-uh/pathos/actions/workflows/ci.yml/badge.svg)](https://github.com/gia-uh/pathos/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/pathos-ai.svg)](https://pypi.org/project/pathos-ai/)
[![Python](https://img.shields.io/pypi/pyversions/pathos-ai.svg)](https://pypi.org/project/pathos-ai/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Production-ready classical AI search algorithms for Python. No machine learning. Pure search.

**[Documentation](https://gia-uh.github.io/pathos)** · [PyPI](https://pypi.org/project/pathos-ai/) · [Examples](examples/)

## Philosophy

Define your *problem*, not your algorithm. PATHOS inspects the capabilities you declare and selects the best algorithm automatically.

```python
from pathos import Space

space = Space().initial("Madrid")

@space.successors
def neighbors(city):
    for next_city, km in roads[city]:
        yield next_city, next_city

@space.goal
def reached(city): return city == "Lisboa"

@space.heuristic
def h(city): return straight_line_km(city, "Lisboa")

result = space.solver().solve()
# → Uses A* automatically (has successors + goal + heuristic)
print(result.path, result.cost, result.algorithm)
```

## Install

```bash
pip install pathos-ai
```

## Algorithm Families

| Declare | Algorithms Available |
|---------|---------------------|
| `@evaluate` | Simulated Annealing, Genetic Algorithm, DE, PSO |
| `@successors + @goal` | BFS, DFS, IDDFS *(DFS is non-optimal — for shortest paths prefer BFS/UCS)* |
| `@successors + @evaluate` | Hill Climbing, Tabu Search, Simulated Annealing — cascaded by `AnytimeLocal` under `mode="auto"` |
| `@successors + @goal + @heuristic` | A*, IDA*, Greedy Best-First |
| `@successors + @goal + @heuristic + @evaluate` | Weighted A*, UCS |
| `.adversarial() + @terminal + @utility` | Minimax, Alpha-Beta, Negamax, MCTS — iterative-deepened by `AnytimeAdversarial` under `mode="auto"` with PV-first move ordering |
| `CSPSpace + @constraint` | Backtracking, Forward Checking, Min-Conflicts (with `@evaluate`) — cascaded by `AnytimeCSP` under `mode="auto"` |

## Specialized Spaces

```python
from pathos import GraphSpace, CSPSpace, TourSpace, GameSpace

# Graph search (auto-provides successors from adjacency)
space = GraphSpace(graph=city_graph).initial("A")

# Constraint satisfaction (auto-provides successors + goal)
csp = CSPSpace(variables=["X", "Y", "Z"])

# Tour optimization (TSP — auto-provides 2-opt neighborhood)
tour = TourSpace(nodes=cities, distances=dist_matrix)

# Adversarial games (auto-sets adversarial mode)
game = GameSpace().initial(board)
```

## Modes — exact, approximate, auto

The `solver()` factory selects an algorithm based on the space's declared
capabilities and the mode you ask for. Three modes:

```python
# Default: anytime cascade. Always-ready, gives best incumbent under
# the budget (1h if you don't set one). Optimal if it finishes.
space.solver().solve()
space.solver(timeout=60).solve()

# Single-shot admissible algorithm. No timeout default; runs to
# completion or until you cut it off.
space.solver(mode="exact").solve()

# Single-shot bounded-suboptimal algorithm. Faster than exact.
space.solver(mode="approximate").solve()
```

Under `mode="auto"` (default), `AnytimeAStar` wins selection on A\*-family
spaces and runs a cascade `[Greedy, WAStar(5,3,2,1.5), AStar]`, keeping
the best incumbent across phases. On a generous budget the final A\* phase
returns the proven-optimal answer; on a tight budget you get the best
incumbent so far instead of `not_found`.

On CSP-shaped spaces (`CSPSpace` or any space whose initial state is a
partial-assignment dict), `AnytimeCSP` wins selection under `mode="auto"`
and runs a cascade `[MinConflicts, Backtracking]` — MinConflicts is only
included when `@evaluate` is declared (it greedily picks the lowest-
violation child). The first phase that returns a consistent complete
assignment wins; if MinConflicts gives up, Backtracking takes over.

On pure-optimization spaces (`@successors + @evaluate`, no `@goal`),
`AnytimeLocal` wins selection under `mode="auto"` and runs a cascade
`[HillClimbing, SimulatedAnnealing, TabuSearch]` — a fast-probe followed
by two escape phases. The best (lowest-cost) incumbent across all phases
is returned; cancellation mid-cascade keeps whichever incumbent was
already planted. `AnytimeLocal` does not declare `@goal`, so on
goal-bearing spaces the selector keeps `AnytimeAStar` (with `@heuristic`)
or the uninformed goal algorithms in charge.

On adversarial spaces (`.adversarial() + @terminal + @utility`),
`AnytimeAdversarial` wins selection under `mode="auto"` and iteratively
deepens from depth 1 to `max_depth` over `AlphaBeta` (2-player) or
`Negamax` (3+ player), threading the previous depth's principal
variation as `pv_hint` into the next phase for move ordering — α-β
pruning becomes substantially more effective with a good first move.
Cancellation mid-deepening keeps the best move from the deepest
completed depth.

`SearchResult.epsilon` tells you the quality bound: `1.0` is proven
optimal, `>1.0` is ε-bounded (cost ≤ ε × optimal), `inf` is unbounded
(greedy), `None` means the algorithm doesn't report a bound (e.g.
metaheuristics).

```python
result = space.solver(timeout=10).solve()
if result.optimal:
    print(f"Optimal: cost {result.cost}")
else:
    print(f"ε-bounded ({result.epsilon}): cost {result.cost}")
```

Every metaheuristic is naturally anytime regardless of mode — set a
timeout and the algorithm returns its best individual seen so far
when the budget runs out (cooperative `CancelToken` protocol).

## Parallel Evaluation

Population-based algorithms (GA, DE, LocalBeamSearch) support multiprocessing via `.parallel(n)`:

```python
# Evaluate all candidates in parallel across 4 processes
space = Space().initial(lambda: random_genome()).parallel(4)

# evaluate fn must be a module-level function (picklable)
def fitness(genome): return -sum(genome)
space.evaluate(fitness)

result = GeneticAlgorithm(space, pop_size=200, generations=500).solve()
```

Pass `n=1` (default) for serial execution. Falls back automatically when population size is 1.

## Direct Algorithm Access

```python
from pathos.algorithms import AStar, GeneticAlgorithm, AlphaBeta

result = AStar(space).solve()  # bypass auto-selection
```

## SearchResult

Every algorithm returns a uniform `SearchResult`:

```python
result.solution      # final state
result.path          # list of (action, state) steps
result.cost          # total cost
result.algorithm     # algorithm name
result.nodes_expanded
result.elapsed       # seconds
result.found         # bool
```

## Performance

Reference numbers from `python -m benchmarks.bench --repeat 3` on an Intel
i7-6820HQ @ 2.70 GHz, Python 3.13. Algorithms are the ones auto-selected by
`space.solver()`. Reproduce with the same command; raw records dumped via
`--json`.

**N-Queens (Backtracking, CSPSpace)**

| N | elapsed (s, median) | nodes expanded |
|---|---:|---:|
| 6  | 0.0003 | 31 |
| 8  | 0.0022 | 113 |
| 10 | 0.0031 | 102 |
| 12 | 0.0149 | 261 |
| 14 | 0.1726 | 1 899 |
| 16 | 1.2363 | 10 052 |

**TSP (TabuSearch, TourSpace, 100 iters)**

| cities | elapsed (s, median) | tour cost (median) |
|---:|---:|---:|
| 5  | 0.0014 | 197.8 |
| 8  | 0.0060 | 253.3 |
| 12 | 0.0224 | 272.2 |
| 16 | 0.0439 | 354.9 |
| 20 | 0.0800 | 383.4 |
| 25 | 0.1593 | 409.3 |

**8-Puzzle (A\* + Manhattan)**

| scramble depth | elapsed (s, median) | nodes expanded | solution length |
|---:|---:|---:|---:|
| 10 | 0.0001 | 12    | 10 |
| 20 | 0.0042 | 461   | 20 |
| 30 | 0.0097 | 1 397 | 24 |
| 40 | 0.0127 | 1 728 | 26 |
| 50 | 0.0086 | 1 186 | 22 |

Solution length plateaus around 22–26 because the 8-puzzle state-space
diameter is ~31 — deeper scrambles don't make harder instances.

## Examples

- [Route Planning (A*)](examples/route_planning.py)
- [TSP (SA + GA)](examples/tsp.py)
- [N-Queens (CSP)](examples/nqueens.py)
- [Tic-tac-toe (Alpha-Beta)](examples/tictactoe.py)
- [8-Puzzle (A*)](examples/puzzle8.py)

## License

MIT — [gia-uh](https://github.com/gia-uh)
