PyRCH 0.0.1__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.
- pyrch-0.0.1/.github/workflows/publish-pypi.yml +84 -0
- pyrch-0.0.1/.gitignore +54 -0
- pyrch-0.0.1/PKG-INFO +444 -0
- pyrch-0.0.1/README.md +421 -0
- pyrch-0.0.1/cpp/CMakeLists.txt +20 -0
- pyrch-0.0.1/cpp/README.md +41 -0
- pyrch-0.0.1/cpp/prog/json_solver.cpp +256 -0
- pyrch-0.0.1/cpp/src/binary_set.hpp +202 -0
- pyrch-0.0.1/cpp/src/debug.hpp +36 -0
- pyrch-0.0.1/cpp/src/graph.hpp +608 -0
- pyrch-0.0.1/cpp/src/include/experiment_map.hpp +162 -0
- pyrch-0.0.1/cpp/src/include/experiment_runner.hpp +152 -0
- pyrch-0.0.1/cpp/src/include/search_LKH_mtsp.hpp +178 -0
- pyrch-0.0.1/cpp/src/include/search_greedy_mtsp.hpp +153 -0
- pyrch-0.0.1/cpp/src/include/solver_types.hpp +69 -0
- pyrch-0.0.1/cpp/src/mtsp/constraint.hpp +99 -0
- pyrch-0.0.1/cpp/src/mtsp/options.hpp +27 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/composite_constraint_checker.cpp +80 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/composite_constraint_checker.hpp +63 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/default_constraint_checker.cpp +42 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/default_constraint_checker.hpp +66 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/time_limit_constraint_checker.cpp +17 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/time_limit_constraint_checker.hpp +38 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/time_window_constraint_checker.cpp +209 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/constraint_checker/time_window_constraint_checker.hpp +104 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/expansion/default_expansion.cpp +24 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/expansion/default_expansion.hpp +42 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/focal_comparator/default_focal_comparator.cpp +23 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/focal_comparator/default_focal_comparator.hpp +43 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/focal_comparator/fvalue_focal_comparator.cpp +23 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/focal_comparator/fvalue_focal_comparator.hpp +43 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/heuristic/default_heuristic.cpp +81 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/heuristic/default_heuristic.hpp +73 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/heuristic/zero_heuristic.hpp +42 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/objective/default_objective.cpp +33 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/objective/default_objective.hpp +42 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/objective/min_sum_objective.cpp +23 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/objective/min_sum_objective.hpp +40 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/plugin.hpp +291 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/post_optimizer/composite_post_optimizer.hpp +43 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/post_optimizer/default_post_optimizer.cpp +586 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/post_optimizer/default_post_optimizer.hpp +252 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/post_optimizer/no_op_post_optimizer.hpp +35 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/post_optimizer/route_merging_post_optimizer.cpp +318 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins/post_optimizer/route_merging_post_optimizer.hpp +96 -0
- pyrch-0.0.1/cpp/src/mtsp/plugins.hpp +51 -0
- pyrch-0.0.1/cpp/src/mtsp/problem.cpp +193 -0
- pyrch-0.0.1/cpp/src/mtsp/problem.hpp +79 -0
- pyrch-0.0.1/cpp/src/mtsp/solution.hpp +22 -0
- pyrch-0.0.1/cpp/src/mtsp/solver.hpp +533 -0
- pyrch-0.0.1/cpp/src/mtsp/types.hpp +50 -0
- pyrch-0.0.1/cpp/src/source/binary_set.cpp +15 -0
- pyrch-0.0.1/cpp/src/source/experiment_map.cpp +272 -0
- pyrch-0.0.1/cpp/src/source/experiment_runner.cpp +726 -0
- pyrch-0.0.1/cpp/src/source/graph.cpp +1063 -0
- pyrch-0.0.1/cpp/src/source/graph_io.cpp +197 -0
- pyrch-0.0.1/cpp/src/source/search_LKH_mtsp.cpp +665 -0
- pyrch-0.0.1/cpp/src/source/search_greedy_mtsp.cpp +315 -0
- pyrch-0.0.1/cpp/src/source/solver.cpp +1297 -0
- pyrch-0.0.1/cpp/src/source/solver_types.cpp +50 -0
- pyrch-0.0.1/cpp/src/type_def.hpp +20 -0
- pyrch-0.0.1/cpp/src/utils/bitsets.hpp +210 -0
- pyrch-0.0.1/cpp/src/utils/path.hpp +31 -0
- pyrch-0.0.1/cpp/src/utils/statis.hpp +53 -0
- pyrch-0.0.1/cpp/src/utils/timer.hpp +39 -0
- pyrch-0.0.1/cpp/src/vec_type.hpp +299 -0
- pyrch-0.0.1/cpp/third_party/CLI11.hpp +11527 -0
- pyrch-0.0.1/cpp/third_party/catch.hpp +17976 -0
- pyrch-0.0.1/cpp/third_party/json.hpp +25540 -0
- pyrch-0.0.1/docs/architecture.md +167 -0
- pyrch-0.0.1/docs/tools.md +87 -0
- pyrch-0.0.1/example/01_solve_from_dict.py +20 -0
- pyrch-0.0.1/example/02_solve_from_json_inputs.py +31 -0
- pyrch-0.0.1/example/03_planner_api.py +18 -0
- pyrch-0.0.1/example/04_low_level_api.py +90 -0
- pyrch-0.0.1/example/05_visualization.py +45 -0
- pyrch-0.0.1/example/README.md +61 -0
- pyrch-0.0.1/example/common.py +120 -0
- pyrch-0.0.1/example/data/problem.json +115 -0
- pyrch-0.0.1/example/data/verify_problem.json +40 -0
- pyrch-0.0.1/example/output/planner_map.png +0 -0
- pyrch-0.0.1/example/output/planner_result.png +0 -0
- pyrch-0.0.1/example/verify_examples.py +160 -0
- pyrch-0.0.1/pyproject.toml +38 -0
- pyrch-0.0.1/python/CMakeLists.txt +33 -0
- pyrch-0.0.1/python/rch_bind.cpp +244 -0
- pyrch-0.0.1/rch/__init__.py +77 -0
- pyrch-0.0.1/rch/_api.py +286 -0
- pyrch-0.0.1/rch/_viz.py +243 -0
- pyrch-0.0.1/tools/compile +8 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Publish PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
tags:
|
|
7
|
+
- "v*"
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build_wheels:
|
|
11
|
+
name: Build wheel on ${{ matrix.os }}
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up QEMU
|
|
22
|
+
if: runner.os == 'Linux' && runner.arch == 'X64'
|
|
23
|
+
uses: docker/setup-qemu-action@v3
|
|
24
|
+
with:
|
|
25
|
+
platforms: arm64
|
|
26
|
+
|
|
27
|
+
- uses: pypa/cibuildwheel@v2.20.0
|
|
28
|
+
env:
|
|
29
|
+
CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-*
|
|
30
|
+
CIBW_SKIP: "*-musllinux_* pp* *-win32 *-manylinux_i686"
|
|
31
|
+
CIBW_ARCHS_LINUX: "auto aarch64"
|
|
32
|
+
CIBW_TEST_COMMAND: python -c "import rch; print(rch.__version__)"
|
|
33
|
+
CIBW_TEST_SKIP: "*-*linux_aarch64"
|
|
34
|
+
|
|
35
|
+
- uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: wheels-${{ matrix.os }}
|
|
38
|
+
path: wheelhouse/*.whl
|
|
39
|
+
|
|
40
|
+
build_sdist:
|
|
41
|
+
name: Build sdist
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
|
|
47
|
+
- uses: actions/setup-python@v5
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.12"
|
|
50
|
+
|
|
51
|
+
- name: Install build frontend
|
|
52
|
+
run: python -m pip install --upgrade build
|
|
53
|
+
|
|
54
|
+
- name: Build sdist
|
|
55
|
+
run: python -m build --sdist
|
|
56
|
+
|
|
57
|
+
- uses: actions/upload-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: sdist
|
|
60
|
+
path: dist/*.tar.gz
|
|
61
|
+
|
|
62
|
+
publish:
|
|
63
|
+
name: Publish to PyPI
|
|
64
|
+
needs: [build_wheels, build_sdist]
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
|
67
|
+
permissions:
|
|
68
|
+
id-token: write
|
|
69
|
+
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/download-artifact@v4
|
|
72
|
+
with:
|
|
73
|
+
path: dist
|
|
74
|
+
|
|
75
|
+
- name: Flatten artifacts
|
|
76
|
+
shell: bash
|
|
77
|
+
run: |
|
|
78
|
+
mkdir -p publish-dist
|
|
79
|
+
find dist -type f \( -name "*.whl" -o -name "*.tar.gz" \) -exec cp {} publish-dist/ \;
|
|
80
|
+
ls -lah publish-dist
|
|
81
|
+
|
|
82
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
83
|
+
with:
|
|
84
|
+
packages-dir: publish-dist
|
pyrch-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Prerequisites
|
|
2
|
+
*.d
|
|
3
|
+
|
|
4
|
+
# Compiled Object files
|
|
5
|
+
*.slo
|
|
6
|
+
*.lo
|
|
7
|
+
*.o
|
|
8
|
+
*.obj
|
|
9
|
+
|
|
10
|
+
# Precompiled Headers
|
|
11
|
+
*.gch
|
|
12
|
+
*.pch
|
|
13
|
+
|
|
14
|
+
# Linker files
|
|
15
|
+
*.ilk
|
|
16
|
+
|
|
17
|
+
# Debugger Files
|
|
18
|
+
*.pdb
|
|
19
|
+
|
|
20
|
+
# Compiled Dynamic libraries
|
|
21
|
+
*.so
|
|
22
|
+
*.dylib
|
|
23
|
+
*.dll
|
|
24
|
+
|
|
25
|
+
# Fortran module files
|
|
26
|
+
*.mod
|
|
27
|
+
*.smod
|
|
28
|
+
|
|
29
|
+
# Compiled Static libraries
|
|
30
|
+
*.lai
|
|
31
|
+
*.la
|
|
32
|
+
*.a
|
|
33
|
+
*.lib
|
|
34
|
+
|
|
35
|
+
# Executables
|
|
36
|
+
*.exe
|
|
37
|
+
*.out
|
|
38
|
+
*.app
|
|
39
|
+
|
|
40
|
+
# debug information files
|
|
41
|
+
*.dwo
|
|
42
|
+
|
|
43
|
+
.vscode
|
|
44
|
+
|
|
45
|
+
__pycache__
|
|
46
|
+
|
|
47
|
+
heha_tw_subset_ablation/
|
|
48
|
+
pure_tw_ablation/
|
|
49
|
+
heha_tw_*/
|
|
50
|
+
heha_dtw_*/
|
|
51
|
+
|
|
52
|
+
.venv/
|
|
53
|
+
build/
|
|
54
|
+
dist/
|
pyrch-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: PyRCH
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: RCH: Partial-Expansion Anytime Focal Search solver for heterogeneous multi-agent TSP
|
|
5
|
+
Keywords: mtsp,tsp,multi-agent,path-planning,routing,heterogeneous-agents
|
|
6
|
+
Author: MTSP-Solver Team
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: C++
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Project-URL: Homepage, https://github.com/rap-lab-org/dev_peaf
|
|
15
|
+
Project-URL: Repository, https://github.com/rap-lab-org/dev_peaf
|
|
16
|
+
Project-URL: Issues, https://github.com/rap-lab-org/dev_peaf/issues
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest; extra == "dev"
|
|
20
|
+
Provides-Extra: viz
|
|
21
|
+
Requires-Dist: matplotlib>=3.8; extra == "viz"
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# RCH Solver
|
|
25
|
+
|
|
26
|
+
**RCH** — a solver for the **Heterogeneous Multi-Agent Travelling Salesman Problem (MTSP)**.
|
|
27
|
+
|
|
28
|
+
This package wraps the high-performance C++ solver core via [pybind11](https://github.com/pybind/pybind11) and makes it available as a regular Python library.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Prerequisites
|
|
35
|
+
|
|
36
|
+
- Python >= 3.8
|
|
37
|
+
- A C++17 compiler (`g++ >= 7`, `clang++ >= 5`)
|
|
38
|
+
- CMake >= 3.15
|
|
39
|
+
- pip
|
|
40
|
+
- matplotlib (only needed for visualization helpers)
|
|
41
|
+
|
|
42
|
+
### Install from PyPI
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install PyRCH
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you also want plotting helpers:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install "PyRCH[viz]"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Install from source
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cd MTSP-Solver
|
|
58
|
+
|
|
59
|
+
# Install (builds the C++ extension automatically)
|
|
60
|
+
pip install .
|
|
61
|
+
|
|
62
|
+
# Or, for development (editable install):
|
|
63
|
+
pip install -e .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **Note**: On Ubuntu 24+ managed Python installs, you may need to add `--break-system-packages` or use a virtual environment.
|
|
67
|
+
|
|
68
|
+
## Release To PyPI
|
|
69
|
+
|
|
70
|
+
If you are publishing manually from a Linux development machine, upload the **sdist** only:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m build --sdist
|
|
74
|
+
twine check dist/*
|
|
75
|
+
twine upload dist/pyrch-*.tar.gz
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> **Why not upload the local wheel?** A wheel built locally on Linux is typically tagged like `linux_x86_64` or `linux_aarch64`. PyPI rejects those platform tags. Linux wheels uploaded to PyPI should be built as compliant `manylinux` wheels, which this repository produces through GitHub Actions.
|
|
79
|
+
|
|
80
|
+
To publish wheels:
|
|
81
|
+
|
|
82
|
+
1. Configure PyPI Trusted Publishing for this repository.
|
|
83
|
+
2. Push a version tag such as `v0.0.1`.
|
|
84
|
+
3. Let `.github/workflows/publish-pypi.yml` build and upload the wheels plus sdist.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
### 1. Solve a JSON problem file
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import rch
|
|
94
|
+
|
|
95
|
+
result = rch.solve("path/to/problem.json", time_limit=10)
|
|
96
|
+
|
|
97
|
+
print(result["status"]) # "success" or "failed"
|
|
98
|
+
print(result["timeout"]) # True if time-limit was reached
|
|
99
|
+
print(result["statistics"]) # {"max_cost": ..., "sum_cost": ..., "solve_time": ..., ...}
|
|
100
|
+
|
|
101
|
+
for route in result["routes"]:
|
|
102
|
+
print(f"Agent {route['agent_id']}: path={route['path']}, cost={route['cost']}")
|
|
103
|
+
|
|
104
|
+
# Anytime improvement history
|
|
105
|
+
for snapshot in result["anytime"]:
|
|
106
|
+
print(f" t={snapshot['time']:.3f}s max_cost={snapshot['max_cost']:.2f}")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 2. Solve from a Python dict
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import rch
|
|
113
|
+
|
|
114
|
+
problem_data = {
|
|
115
|
+
"nodes": [
|
|
116
|
+
{"id": 0, "x": 0.0, "y": 0.0, "type": "depot"},
|
|
117
|
+
{"id": 1, "x": 1.0, "y": 2.0, "type": "target"},
|
|
118
|
+
{"id": 2, "x": 3.0, "y": 1.0, "type": "target"},
|
|
119
|
+
],
|
|
120
|
+
"agents": [
|
|
121
|
+
{"id": 0, "type": "UAV", "start_node": 0, "end_node": 0},
|
|
122
|
+
{"id": 1, "type": "UAV", "start_node": 0, "end_node": 0},
|
|
123
|
+
],
|
|
124
|
+
"costs": {
|
|
125
|
+
"UAV": [
|
|
126
|
+
[0.0, 2.24, 3.16],
|
|
127
|
+
[2.24, 0.0, 2.24],
|
|
128
|
+
[3.16, 2.24, 0.0],
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"options": {
|
|
132
|
+
"return_to_end": True,
|
|
133
|
+
"objective": "min_max"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
result = rch.solve(problem_data, time_limit=5)
|
|
138
|
+
print(result)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3. Planner API (recommended)
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import rch
|
|
145
|
+
|
|
146
|
+
planner = rch.Planner()
|
|
147
|
+
|
|
148
|
+
# Add nodes
|
|
149
|
+
planner.add_depot(0, x=0.0, y=0.0)
|
|
150
|
+
planner.add_target(1, x=1.0, y=2.0)
|
|
151
|
+
planner.add_target(2, x=3.0, y=1.0)
|
|
152
|
+
|
|
153
|
+
# Add agents
|
|
154
|
+
planner.add_agent(agent_id=0, agent_type="UAV", start_node=0, end_node=0, time_limit=6.0)
|
|
155
|
+
planner.add_agent(agent_id=1, agent_type="UGV", start_node=0, end_node=0, time_limit=9.0)
|
|
156
|
+
|
|
157
|
+
# Set cost matrices (one per agent type)
|
|
158
|
+
planner.set_cost_matrix("UAV", [
|
|
159
|
+
[0.0, 2.24, 3.16],
|
|
160
|
+
[2.24, 0.0, 2.24],
|
|
161
|
+
[3.16, 2.24, 0.0],
|
|
162
|
+
])
|
|
163
|
+
planner.set_cost_matrix("UGV", [
|
|
164
|
+
[0.0, 1.80, 4.20],
|
|
165
|
+
[1.80, 0.0, 2.90],
|
|
166
|
+
[4.20, 2.90, 0.0],
|
|
167
|
+
])
|
|
168
|
+
|
|
169
|
+
# Add constraints (optional)
|
|
170
|
+
planner.add_assignment(1, ["UAV"])
|
|
171
|
+
planner.add_assignment(2, ["UGV"])
|
|
172
|
+
planner.add_time_window(1, start=0.0, end=3.0)
|
|
173
|
+
planner.add_time_window(2, start=0.0, end=6.0)
|
|
174
|
+
|
|
175
|
+
# Set solver options
|
|
176
|
+
planner.set_options(return_to_end=True, objective="min_max", time_limit=5)
|
|
177
|
+
|
|
178
|
+
# Visualize the problem map
|
|
179
|
+
planner.show_map()
|
|
180
|
+
|
|
181
|
+
# Solve
|
|
182
|
+
result = planner.solve()
|
|
183
|
+
print(result["status"])
|
|
184
|
+
for route in result["routes"]:
|
|
185
|
+
print(f" Agent {route['agent_id']}: path={route['path']}, cost={route['cost']:.3f}")
|
|
186
|
+
|
|
187
|
+
# Visualize the result
|
|
188
|
+
planner.show_result(result)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 4. Visualization
|
|
192
|
+
|
|
193
|
+
#### `show_map` — view the problem before solving
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# Via Planner method:
|
|
197
|
+
planner.show_map()
|
|
198
|
+
|
|
199
|
+
# Or via module-level function:
|
|
200
|
+
rch.show_map(planner)
|
|
201
|
+
|
|
202
|
+
# Draw on an existing axes (e.g. for subplots):
|
|
203
|
+
import matplotlib.pyplot as plt
|
|
204
|
+
fig, ax = plt.subplots()
|
|
205
|
+
rch.show_map(planner, ax=ax, show=False)
|
|
206
|
+
plt.savefig("map.png")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Depots are drawn as black stars (★), targets as grey dots, and each agent's start position as a coloured triangle.
|
|
210
|
+
|
|
211
|
+
#### `show_result` — view routes after solving
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
result = planner.solve()
|
|
215
|
+
|
|
216
|
+
# Via Planner method:
|
|
217
|
+
planner.show_result(result)
|
|
218
|
+
|
|
219
|
+
# Or via module-level function:
|
|
220
|
+
rch.show_result(planner, result)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Each agent's route is drawn with a distinct colour and directional arrows.
|
|
224
|
+
|
|
225
|
+
**`return_to_end=False` handling**: when the solver option `return_to_end` is `False`, the solver internally appends the depot as the last node in each route. `show_result` automatically detects this and removes the trailing depot from the visualization — the route will end at the last target visited, without drawing an edge back to the depot.
|
|
226
|
+
|
|
227
|
+
### 5. Low-level programmatic API
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from rch import Problem, Solver, Options, ObjectiveType, NodeType
|
|
231
|
+
from rch import Node, Agent, AssignmentConstraint, TimeWindowConstraint
|
|
232
|
+
|
|
233
|
+
# Build a problem instance in code
|
|
234
|
+
problem = Problem()
|
|
235
|
+
|
|
236
|
+
# Add nodes
|
|
237
|
+
for nid, (x, y), ntype in [(0, (0, 0), NodeType.DEPOT),
|
|
238
|
+
(1, (1, 2), NodeType.TARGET),
|
|
239
|
+
(2, (3, 1), NodeType.TARGET)]:
|
|
240
|
+
n = Node()
|
|
241
|
+
n.id = nid
|
|
242
|
+
n.position.x = x
|
|
243
|
+
n.position.y = y
|
|
244
|
+
n.type = ntype
|
|
245
|
+
problem.add_node(n)
|
|
246
|
+
|
|
247
|
+
# Add agents
|
|
248
|
+
a = Agent()
|
|
249
|
+
a.id = 0
|
|
250
|
+
a.type = "UAV"
|
|
251
|
+
a.start_node = 0
|
|
252
|
+
a.end_node = 0
|
|
253
|
+
problem.add_agent(a, 0)
|
|
254
|
+
|
|
255
|
+
a2 = Agent()
|
|
256
|
+
a2.id = 1
|
|
257
|
+
a2.type = "UAV"
|
|
258
|
+
a2.start_node = 0
|
|
259
|
+
a2.end_node = 0
|
|
260
|
+
problem.add_agent(a2, 1)
|
|
261
|
+
|
|
262
|
+
# Set cost matrix (one per agent type)
|
|
263
|
+
import math
|
|
264
|
+
nodes_xy = [(0,0), (1,2), (3,1)]
|
|
265
|
+
n = len(nodes_xy)
|
|
266
|
+
cost = [[0.0]*n for _ in range(n)]
|
|
267
|
+
for i in range(n):
|
|
268
|
+
for j in range(n):
|
|
269
|
+
dx = nodes_xy[i][0] - nodes_xy[j][0]
|
|
270
|
+
dy = nodes_xy[i][1] - nodes_xy[j][1]
|
|
271
|
+
cost[i][j] = math.sqrt(dx*dx + dy*dy)
|
|
272
|
+
problem.set_cost_matrix("UAV", cost)
|
|
273
|
+
|
|
274
|
+
# Configure options
|
|
275
|
+
problem.options().objective = ObjectiveType.MinMax
|
|
276
|
+
problem.options().return_to_end = True
|
|
277
|
+
problem.options().time_limit = 5.0
|
|
278
|
+
|
|
279
|
+
# Solve
|
|
280
|
+
opts = problem.options()
|
|
281
|
+
solver = Solver(problem, opts)
|
|
282
|
+
ret = solver.solve()
|
|
283
|
+
result = solver.get_result()
|
|
284
|
+
|
|
285
|
+
# Note: this low-level API follows the original C++ convention:
|
|
286
|
+
# ret == 1 means success, ret == 0 means failure.
|
|
287
|
+
print(f"Return code: {ret}")
|
|
288
|
+
print(f"Paths: {result.paths}")
|
|
289
|
+
print(f"Costs: {result.times}")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## JSON Input Format
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"nodes": [
|
|
299
|
+
{"id": 0, "x": 0.0, "y": 0.0, "z": 0.0, "type": "depot"},
|
|
300
|
+
{"id": 1, "x": 1.5, "y": 2.3, "z": 0.0, "type": "target"}
|
|
301
|
+
],
|
|
302
|
+
"agents": [
|
|
303
|
+
{
|
|
304
|
+
"id": 0,
|
|
305
|
+
"type": "TypeA",
|
|
306
|
+
"start_node": 0,
|
|
307
|
+
"end_node": 0,
|
|
308
|
+
"max_length": 100.0,
|
|
309
|
+
"capacity": 10.0
|
|
310
|
+
}
|
|
311
|
+
],
|
|
312
|
+
"costs": {
|
|
313
|
+
"TypeA": [[0.0, 1.5], [1.5, 0.0]]
|
|
314
|
+
},
|
|
315
|
+
"constraints": [
|
|
316
|
+
{
|
|
317
|
+
"kind": "assignment",
|
|
318
|
+
"items": [
|
|
319
|
+
{"node": 1, "types": ["TypeA"]}
|
|
320
|
+
]
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"kind": "timewindow",
|
|
324
|
+
"items": [
|
|
325
|
+
{"node": 1, "start": 0.0, "end": 50.0}
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
],
|
|
329
|
+
"options": {
|
|
330
|
+
"return_to_end": true,
|
|
331
|
+
"objective": "min_max",
|
|
332
|
+
"time_limit": 60
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Fields
|
|
338
|
+
|
|
339
|
+
| Field | Description |
|
|
340
|
+
|---|---|
|
|
341
|
+
| `nodes` | List of nodes. Each has `id`, `x`, `y`, optional `z`, `type` (`"depot"` or `"target"`). |
|
|
342
|
+
| `agents` | List of agents. Each has `id`, `type`, `start_node`, `end_node`, optional `max_length`, `capacity`. |
|
|
343
|
+
| `costs` | Dict mapping agent type name → cost matrix (2D array, row = from node id, col = to node id). |
|
|
344
|
+
| `constraints` | Optional. List of constraint blocks. `kind` = `"assignment"` or `"timewindow"`. |
|
|
345
|
+
| `options` | Optional. `return_to_end` (bool), `objective` (`"min_max"` or `"min_sum"`), `time_limit` (seconds). |
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Result Format
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
{
|
|
353
|
+
"status": "success", # "success" or "failed"
|
|
354
|
+
"timeout": False, # True if solver hit time limit
|
|
355
|
+
"routes": [
|
|
356
|
+
{
|
|
357
|
+
"agent_id": 0,
|
|
358
|
+
"path": [0, 2, 0], # ordered node IDs
|
|
359
|
+
"cost": 6.32 # route cost
|
|
360
|
+
},
|
|
361
|
+
...
|
|
362
|
+
],
|
|
363
|
+
"statistics": {
|
|
364
|
+
"solve_time": 0.123, # wall-clock time (s)
|
|
365
|
+
"max_cost": 6.32, # maximum route cost
|
|
366
|
+
"sum_cost": 10.56, # total cost of all routes
|
|
367
|
+
"n_generated": 1500, # labels generated
|
|
368
|
+
"n_expanded": 800, # labels expanded
|
|
369
|
+
"last_update_time": 0.08 # time of last solution improvement
|
|
370
|
+
},
|
|
371
|
+
"anytime": [
|
|
372
|
+
{"time": 0.01, "max_cost": 9.5, "sum_cost": 15.2},
|
|
373
|
+
{"time": 0.05, "max_cost": 7.1, "sum_cost": 12.0},
|
|
374
|
+
...
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## API Reference
|
|
382
|
+
|
|
383
|
+
### `rch.solve(source, *, time_limit=-1) → dict`
|
|
384
|
+
|
|
385
|
+
Solve an MTSP instance.
|
|
386
|
+
|
|
387
|
+
- `source` — file path (`str` / `Path`), raw JSON string, or Python `dict`.
|
|
388
|
+
- `time_limit` — override the time limit in seconds (default: use the value in JSON).
|
|
389
|
+
|
|
390
|
+
### `rch.Planner` (recommended)
|
|
391
|
+
|
|
392
|
+
High-level builder API. All mutating methods return `self` for chaining.
|
|
393
|
+
|
|
394
|
+
- `add_depot(node_id, *, x, y, z=0)` — add a depot node.
|
|
395
|
+
- `add_target(node_id, *, x, y, z=0, demand=0)` — add a target node.
|
|
396
|
+
- `add_agent(*, agent_id, agent_type, start_node, end_node, order=None, capacity_limit=-1, time_limit=-1)` — add an agent. `time_limit` is the max travel distance/time (≤0 means no limit).
|
|
397
|
+
- `set_cost_matrix(agent_type, matrix)` — set cost matrix for an agent type.
|
|
398
|
+
- `add_assignment(node_id, types)` — assign a node to a list of allowed agent types.
|
|
399
|
+
- `add_time_window(node_id, start, end)` — add a time window constraint for a node.
|
|
400
|
+
- `set_options(*, return_to_end=None, objective=None, time_limit=None)` — set solver options. `objective` accepts `"min_max"` or `"min_sum"`.
|
|
401
|
+
- `solve() → dict` — run the solver and return a result dict.
|
|
402
|
+
- `show_map(**kwargs) → Axes` — visualize the problem map (depots, targets, agent starts).
|
|
403
|
+
- `show_result(result, **kwargs) → Axes` — visualize solved routes on the map.
|
|
404
|
+
|
|
405
|
+
### `rch.show_map(planner, *, ax=None, figsize=(8,6), show=True) → Axes`
|
|
406
|
+
|
|
407
|
+
Plot the problem map: depots (★), targets (●), and agent start positions (▲).
|
|
408
|
+
|
|
409
|
+
### `rch.show_result(planner, result, *, ax=None, figsize=(8,6), show=True) → Axes`
|
|
410
|
+
|
|
411
|
+
Plot solved routes on the map. Each agent's path is drawn with a distinct colour and directional arrows. When `return_to_end=False`, the trailing depot is automatically stripped from the visualization.
|
|
412
|
+
|
|
413
|
+
### `rch.Problem`
|
|
414
|
+
|
|
415
|
+
Low-level programmatic problem builder. Methods:
|
|
416
|
+
|
|
417
|
+
- `add_node(node: Node)` — add a node.
|
|
418
|
+
- `add_agent(agent: Agent, id: int)` — add an agent.
|
|
419
|
+
- `set_cost_matrix(agent_type: str, matrix: List[List[float]])` — set cost matrix.
|
|
420
|
+
- `set_assignment_constraint(c: AssignmentConstraint)` — set assignment constraint.
|
|
421
|
+
- `set_timewindow_constraint(c: TimeWindowConstraint)` — set time window constraint.
|
|
422
|
+
- `options() → Options` — access/modify solver options.
|
|
423
|
+
|
|
424
|
+
### `rch.Solver`
|
|
425
|
+
|
|
426
|
+
Low-level solver. Construct with `Solver(problem, options)`.
|
|
427
|
+
|
|
428
|
+
- `solve() → int` — run the solver (1 = success, 0 = failure).
|
|
429
|
+
- `get_result() → Result` — get the final result.
|
|
430
|
+
- `get_result_process() → List[Tuple[float, Result]]` — get full anytime history.
|
|
431
|
+
|
|
432
|
+
### `rch.ObjectiveType`
|
|
433
|
+
|
|
434
|
+
Enum: `ObjectiveType.MinMax`, `ObjectiveType.MinSum`.
|
|
435
|
+
|
|
436
|
+
### `rch.NodeType`
|
|
437
|
+
|
|
438
|
+
Enum: `NodeType.DEPOT`, `NodeType.TARGET`.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## License
|
|
443
|
+
|
|
444
|
+
MIT
|