pgraph-python 0.6.4__tar.gz → 1.0.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.
- pgraph_python-1.0.0/.codacy.yml +7 -0
- pgraph_python-1.0.0/.github/dependabot.yml +8 -0
- pgraph_python-1.0.0/.github/workflows/ci.yml +121 -0
- pgraph_python-1.0.0/.github/workflows/dependabot-auto-merge.yml +25 -0
- pgraph_python-1.0.0/.github/workflows/publish.yml +115 -0
- pgraph_python-1.0.0/AGENTS.md +29 -0
- pgraph_python-1.0.0/CHANGELOG.md +212 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/Makefile +1 -12
- pgraph_python-1.0.0/PKG-INFO +277 -0
- pgraph_python-1.0.0/README.md +236 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/Makefile +5 -1
- pgraph_python-1.0.0/docs/figs/datastructures.ezdraw +3172 -0
- pgraph_python-1.0.0/docs/figs/datastructures.svg +13239 -0
- pgraph_python-1.0.0/docs/figs/london_route.png +0 -0
- pgraph_python-1.0.0/docs/figs/pgraph_logo.png +0 -0
- pgraph_python-1.0.0/docs/figs/pgraph_logo.py +161 -0
- pgraph_python-1.0.0/docs/llms_intro.md +43 -0
- pgraph_python-1.0.0/docs/llms_txt.py +170 -0
- pgraph_python-1.0.0/docs/source/_static/custom.css +15 -0
- pgraph_python-1.0.0/docs/source/_templates/footer.html +13 -0
- pgraph_python-1.0.0/docs/source/analysis.rst +203 -0
- pgraph_python-1.0.0/docs/source/api.rst +9 -0
- pgraph_python-1.0.0/docs/source/common.rst +37 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/source/conf.py +31 -4
- pgraph_python-1.0.0/docs/source/construction.rst +76 -0
- pgraph_python-1.0.0/docs/source/edges_vertices.rst +249 -0
- pgraph_python-1.0.0/docs/source/export.rst +35 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/source/index.rst +4 -11
- pgraph_python-1.0.0/docs/source/intro.rst +304 -0
- pgraph_python-1.0.0/docs/source/modification.rst +55 -0
- pgraph_python-1.0.0/docs/source/planning.rst +20 -0
- pgraph_python-1.0.0/docs/source/policy.rst +94 -0
- pgraph_python-1.0.0/docs/source/representations.rst +25 -0
- pgraph_python-1.0.0/docs/source/visualization.rst +52 -0
- pgraph_python-1.0.0/examples/README.md +37 -0
- pgraph_python-1.0.0/examples/benchmark.py +344 -0
- pgraph_python-1.0.0/examples/london.ipynb +1094 -0
- pgraph_python-1.0.0/examples/london.json +20340 -0
- pgraph_python-1.0.0/examples/osm.py +248 -0
- pgraph_python-1.0.0/examples/roads.ipynb +461 -0
- pgraph_python-1.0.0/examples/roads.png +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/pyproject.toml +15 -7
- pgraph_python-1.0.0/src/pgraph/PGraph.py +5343 -0
- pgraph_python-1.0.0/src/pgraph/__init__.py +33 -0
- pgraph_python-1.0.0/src/pgraph/_plot.py +70 -0
- pgraph_python-1.0.0/src/pgraph/exporters.py +502 -0
- pgraph_python-1.0.0/tests/test_add_edge_edge.py +109 -0
- pgraph_python-1.0.0/tests/test_add_vertex_names.py +116 -0
- pgraph_python-1.0.0/tests/test_adjacency_loops.py +89 -0
- pgraph_python-1.0.0/tests/test_adjacent.py +106 -0
- pgraph_python-1.0.0/tests/test_attribute_hygiene.py +170 -0
- pgraph_python-1.0.0/tests/test_components.py +340 -0
- pgraph_python-1.0.0/tests/test_connect_edge.py +96 -0
- pgraph_python-1.0.0/tests/test_constructor_keywords.py +40 -0
- pgraph_python-1.0.0/tests/test_detach_hook.py +131 -0
- pgraph_python-1.0.0/tests/test_edge_endpoints.py +124 -0
- pgraph_python-1.0.0/tests/test_edges_list.py +97 -0
- pgraph_python-1.0.0/tests/test_edges_to.py +103 -0
- pgraph_python-1.0.0/tests/test_export.py +523 -0
- pgraph_python-1.0.0/tests/test_getitem.py +68 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/tests/test_graph.py +363 -65
- pgraph_python-1.0.0/tests/test_graph_classes.py +176 -0
- pgraph_python-1.0.0/tests/test_highlight_3d.py +128 -0
- pgraph_python-1.0.0/tests/test_in_edges.py +203 -0
- pgraph_python-1.0.0/tests/test_incidence.py +199 -0
- pgraph_python-1.0.0/tests/test_laplacian_name.py +43 -0
- pgraph_python-1.0.0/tests/test_lazy_imports.py +89 -0
- pgraph_python-1.0.0/tests/test_multigraph.py +303 -0
- pgraph_python-1.0.0/tests/test_pickle.py +256 -0
- pgraph_python-1.0.0/tests/test_planner_choices.py +231 -0
- pgraph_python-1.0.0/tests/test_plot_args.py +113 -0
- pgraph_python-1.0.0/tests/test_plot_arrows.py +392 -0
- pgraph_python-1.0.0/tests/test_se2_metric.py +139 -0
- pgraph_python-1.0.0/tests/test_spatial_index.py +414 -0
- pgraph_python-1.0.0/tests/test_subgraph.py +380 -0
- pgraph_python-1.0.0/tests/test_unified_graph.py +265 -0
- pgraph_python-1.0.0/tests/test_validate.py +235 -0
- pgraph_python-1.0.0/tests/test_vertex_hook.py +154 -0
- pgraph_python-1.0.0/tests/test_vertex_init.py +165 -0
- pgraph_python-0.6.4/.github/workflows/ci.yml +0 -77
- pgraph_python-0.6.4/CHANGELOG.md +0 -9
- pgraph_python-0.6.4/PKG-INFO +0 -176
- pgraph_python-0.6.4/README.md +0 -138
- pgraph_python-0.6.4/RELEASE +0 -1
- pgraph_python-0.6.4/build/lib/pgraph/PGraph.py +0 -1918
- pgraph_python-0.6.4/build/lib/pgraph/__init__.py +0 -1
- pgraph_python-0.6.4/docs/source/PGraph.rst +0 -7
- pgraph_python-0.6.4/docs/source/datastructures.ezdraw +0 -2353
- pgraph_python-0.6.4/docs/source/dgraph.rst +0 -9
- pgraph_python-0.6.4/docs/source/directed.rst +0 -9
- pgraph_python-0.6.4/docs/source/dvertex.rst +0 -8
- pgraph_python-0.6.4/docs/source/edge.rst +0 -8
- pgraph_python-0.6.4/docs/source/matrices.rst +0 -7
- pgraph_python-0.6.4/docs/source/modules.rst +0 -7
- pgraph_python-0.6.4/docs/source/planning.rst +0 -7
- pgraph_python-0.6.4/docs/source/ugraph.rst +0 -9
- pgraph_python-0.6.4/docs/source/undirected.rst +0 -9
- pgraph_python-0.6.4/docs/source/uvertex.rst +0 -8
- pgraph_python-0.6.4/examples/README.md +0 -8
- pgraph_python-0.6.4/examples/roads.png +0 -0
- pgraph_python-0.6.4/src/pgraph/PGraph.py +0 -1912
- pgraph_python-0.6.4/src/pgraph/__init__.py +0 -1
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/.gitignore +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/LICENSE +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/make.bat +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/source/datastructures.png +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/create_json.py +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/places.json +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/queensland.json +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/roads.py +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/routes.json +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/posegraph.py +0 -0
- {pgraph_python-0.6.4 → pgraph_python-1.0.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
exclude_paths:
|
|
3
|
+
# Developer scripts that regenerate documentation assets (e.g. the README
|
|
4
|
+
# logo), not part of the installed package. They shell out to fixed
|
|
5
|
+
# commands like rsvg-convert, which Bandit flags (B404/B603/B607) even
|
|
6
|
+
# though no untrusted input is involved.
|
|
7
|
+
- "docs/figs/**"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
unittest:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
+
uses: actions/setup-python@v7
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install .[dev]
|
|
26
|
+
sudo apt-get update
|
|
27
|
+
sudo apt-get install -y graphviz
|
|
28
|
+
- name: Test with pytest
|
|
29
|
+
run: pytest
|
|
30
|
+
|
|
31
|
+
mypy:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v7
|
|
35
|
+
- name: Set up Python 3.11
|
|
36
|
+
uses: actions/setup-python@v7
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.11"
|
|
39
|
+
- name: Install dependencies
|
|
40
|
+
run: |
|
|
41
|
+
python -m pip install --upgrade pip
|
|
42
|
+
pip install .[dev]
|
|
43
|
+
- name: Run mypy
|
|
44
|
+
run: mypy src/pgraph/PGraph.py
|
|
45
|
+
|
|
46
|
+
all-tests-passed:
|
|
47
|
+
name: All tests passed
|
|
48
|
+
needs: unittest
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
if: always()
|
|
51
|
+
steps:
|
|
52
|
+
- name: Check test matrix result
|
|
53
|
+
run: |
|
|
54
|
+
if [ "${{ needs.unittest.result }}" != "success" ]; then
|
|
55
|
+
echo "test matrix did not succeed: ${{ needs.unittest.result }}"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
codecov:
|
|
60
|
+
needs: unittest
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v7
|
|
64
|
+
- name: Set up Python 3.11
|
|
65
|
+
uses: actions/setup-python@v7
|
|
66
|
+
with:
|
|
67
|
+
python-version: "3.11"
|
|
68
|
+
- name: Install dependencies
|
|
69
|
+
run: |
|
|
70
|
+
python -m pip install --upgrade pip
|
|
71
|
+
pip install -e .[dev]
|
|
72
|
+
sudo apt-get update
|
|
73
|
+
sudo apt-get install -y graphviz
|
|
74
|
+
- name: Run coverage
|
|
75
|
+
run: |
|
|
76
|
+
pytest --cov=pgraph --cov-report=xml:coverage.xml
|
|
77
|
+
coverage report
|
|
78
|
+
- name: Upload coverage to Codecov
|
|
79
|
+
uses: codecov/codecov-action@v7
|
|
80
|
+
with:
|
|
81
|
+
files: ./coverage.xml
|
|
82
|
+
verbose: true
|
|
83
|
+
|
|
84
|
+
docs:
|
|
85
|
+
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
|
|
86
|
+
needs: unittest
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
# The site is served from a GitHub Pages deployment, not from the gh-pages
|
|
89
|
+
# branch, so the artifact is deployed with the Pages actions.
|
|
90
|
+
permissions:
|
|
91
|
+
contents: read
|
|
92
|
+
pages: write
|
|
93
|
+
id-token: write
|
|
94
|
+
environment:
|
|
95
|
+
name: github-pages
|
|
96
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v7
|
|
99
|
+
- name: Set up Python 3.11
|
|
100
|
+
uses: actions/setup-python@v7
|
|
101
|
+
with:
|
|
102
|
+
python-version: "3.11"
|
|
103
|
+
- name: Install dependencies
|
|
104
|
+
run: |
|
|
105
|
+
python -m pip install --upgrade pip
|
|
106
|
+
pip install .[docs]
|
|
107
|
+
sudo apt-get update
|
|
108
|
+
sudo apt-get install -y graphviz
|
|
109
|
+
- name: Build docs
|
|
110
|
+
run: |
|
|
111
|
+
make -C docs html llms
|
|
112
|
+
touch docs/build/html/.nojekyll
|
|
113
|
+
- name: Setup Pages
|
|
114
|
+
uses: actions/configure-pages@v6
|
|
115
|
+
- name: Upload artifact
|
|
116
|
+
uses: actions/upload-pages-artifact@v5
|
|
117
|
+
with:
|
|
118
|
+
path: docs/build/html
|
|
119
|
+
- name: Deploy to GitHub Pages
|
|
120
|
+
id: deployment
|
|
121
|
+
uses: actions/deploy-pages@v5
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Dependabot auto-merge
|
|
2
|
+
|
|
3
|
+
on: pull_request
|
|
4
|
+
|
|
5
|
+
permissions:
|
|
6
|
+
contents: write
|
|
7
|
+
pull-requests: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
dependabot:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: ${{ github.actor == 'dependabot[bot]' }}
|
|
13
|
+
steps:
|
|
14
|
+
- name: Dependabot metadata
|
|
15
|
+
id: metadata
|
|
16
|
+
uses: dependabot/fetch-metadata@v3
|
|
17
|
+
with:
|
|
18
|
+
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
19
|
+
|
|
20
|
+
- name: Enable auto-merge for patch/minor bumps
|
|
21
|
+
if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' }}
|
|
22
|
+
run: gh pr merge --auto --merge "$PR_URL"
|
|
23
|
+
env:
|
|
24
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
25
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Build and publish to PyPI when a GitHub release is created, or on manual trigger.
|
|
2
|
+
# Uses OIDC trusted publishing, so no PyPI token is stored. The trusted publisher
|
|
3
|
+
# on PyPI is: owner petercorke, repository pgraph-python, workflow publish.yml,
|
|
4
|
+
# environment pypi. Pure Python package, so one universal wheel and an sdist.
|
|
5
|
+
#
|
|
6
|
+
# To release: make sure main has the version in pyproject.toml, then create a
|
|
7
|
+
# GitHub release whose tag is v<version> on main.
|
|
8
|
+
|
|
9
|
+
name: Publish to PyPI
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [created]
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
verify_version:
|
|
21
|
+
name: Verify tag matches package version
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
- name: Check version
|
|
28
|
+
# Skip on manual workflow_dispatch runs (no release tag exists)
|
|
29
|
+
if: github.event_name == 'release'
|
|
30
|
+
run: |
|
|
31
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
32
|
+
PKG=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
33
|
+
echo "Tag version : $TAG"
|
|
34
|
+
echo "Package version: $PKG"
|
|
35
|
+
[ "$TAG" = "$PKG" ] || { echo "ERROR: tag $GITHUB_REF_NAME does not match pyproject.toml version $PKG"; exit 1; }
|
|
36
|
+
- name: Check the tagged commit is on main
|
|
37
|
+
if: github.event_name == 'release'
|
|
38
|
+
run: |
|
|
39
|
+
git fetch origin main
|
|
40
|
+
git merge-base --is-ancestor "$GITHUB_SHA" origin/main \
|
|
41
|
+
|| { echo "ERROR: $GITHUB_SHA is not on main, so it was not reviewed and tested there"; exit 1; }
|
|
42
|
+
|
|
43
|
+
build:
|
|
44
|
+
name: Build sdist and wheel
|
|
45
|
+
needs: verify_version
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v7
|
|
49
|
+
|
|
50
|
+
- uses: actions/setup-python@v7
|
|
51
|
+
with:
|
|
52
|
+
python-version: "3.12"
|
|
53
|
+
|
|
54
|
+
- name: Build sdist and wheel
|
|
55
|
+
run: pipx run build
|
|
56
|
+
|
|
57
|
+
- name: Check metadata
|
|
58
|
+
run: pipx run twine check dist/*
|
|
59
|
+
|
|
60
|
+
- uses: actions/upload-artifact@v7
|
|
61
|
+
with:
|
|
62
|
+
name: dist
|
|
63
|
+
path: dist/
|
|
64
|
+
|
|
65
|
+
smoke_test:
|
|
66
|
+
name: Smoke-test wheel
|
|
67
|
+
needs: build
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/setup-python@v7
|
|
71
|
+
with:
|
|
72
|
+
python-version: "3.12"
|
|
73
|
+
|
|
74
|
+
- uses: actions/download-artifact@v8
|
|
75
|
+
with:
|
|
76
|
+
name: dist
|
|
77
|
+
path: dist
|
|
78
|
+
|
|
79
|
+
- name: Install the wheel (pulls dependencies from PyPI)
|
|
80
|
+
run: pip install dist/*.whl
|
|
81
|
+
|
|
82
|
+
- name: Smoke test
|
|
83
|
+
run: |
|
|
84
|
+
python - <<'PY'
|
|
85
|
+
from importlib.metadata import version
|
|
86
|
+
import pgraph
|
|
87
|
+
|
|
88
|
+
g = pgraph.UGraph()
|
|
89
|
+
g.add_vertex(coord=[0, 0], name="a")
|
|
90
|
+
g.add_vertex(coord=[3, 4], name="b")
|
|
91
|
+
g.add_edge("a", "b")
|
|
92
|
+
path, length, _ = g.path_Astar("a", "b")
|
|
93
|
+
assert length == 5.0, length
|
|
94
|
+
print("OK", version("pgraph-python"), length)
|
|
95
|
+
PY
|
|
96
|
+
|
|
97
|
+
upload_pypi:
|
|
98
|
+
name: Publish to PyPI
|
|
99
|
+
needs: [build, smoke_test]
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
environment:
|
|
102
|
+
name: pypi
|
|
103
|
+
url: https://pypi.org/project/pgraph-python/
|
|
104
|
+
permissions:
|
|
105
|
+
id-token: write # required for OIDC trusted publishing
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/download-artifact@v8
|
|
108
|
+
with:
|
|
109
|
+
name: dist
|
|
110
|
+
path: dist
|
|
111
|
+
|
|
112
|
+
# pinned to a commit, as this action can publish to PyPI; dependabot updates it
|
|
113
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
114
|
+
with:
|
|
115
|
+
skip-existing: true
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# pgraph-python — Agent Instructions
|
|
2
|
+
|
|
3
|
+
> **Audience:** agents working on pgraph's source code. If you're helping someone *use* pgraph, read https://petercorke.github.io/pgraph-python/llms.txt instead (or `llms-full.txt` for the complete documentation).
|
|
4
|
+
|
|
5
|
+
Part of the RVC ecosystem. **Read [rvc-ecosystem/AGENTS.md](https://github.com/petercorke/rvc-ecosystem/blob/main/AGENTS.md) first** — it defines shared conventions: repo ownership, math invariants, dependency boundaries, git/PR workflow, code standards, tech-debt tracking. This file only adds what's specific to this repo.
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
|---|---|
|
|
9
|
+
| PyPI package | `pgraph-python` |
|
|
10
|
+
| Nickname | pgraph-python |
|
|
11
|
+
| Owner | Peter Corke (`petercorke`) |
|
|
12
|
+
| Default branch | `main` |
|
|
13
|
+
| Contribution model | Branch → PR; `main` is protected: CI must pass and the branch must be up to date (`gh pr update-branch`) |
|
|
14
|
+
|
|
15
|
+
## Notes specific to this repo
|
|
16
|
+
|
|
17
|
+
- Minimal graph/path-planning package. Pure Python, builds with Hatch (`hatchling`); Python 3.9+. Depends on `numpy`, `matplotlib` and `spatialmath-python`; `ansitable` and `scipy` arrive through spatialmath. **Do not add scipy to `pyproject.toml`**: it is imported lazily, only inside `Graph.build_kdtree()`.
|
|
18
|
+
- Used, unpinned (`"pgraph-python"`), by `robotics-toolbox-python` (`PRMPlanner`, `RRTPlanner`, `LatticePlanner`, `PoseGraph`, which subclass `UVertex`/`DVertex`) and `machinevision-toolbox-python` (`BundleAdjust`). A breaking change needs a deprecation cycle with a working fallback (`warnings.warn(..., DeprecationWarning, stacklevel=2)`); run their pgraph-using tests before a release.
|
|
19
|
+
- **Layout.** `src/pgraph/PGraph.py` holds `Graph`, `UGraph`, `DGraph`, `Vertex`, `Edge` and `SE2Metric`; `exporters.py` the `export()` formats. There is one `Vertex` class: a vertex has no direction, the graph decides how an edge is recorded (`UGraph`/`DGraph` implement the hooks `_attach`, `_detach`, `_incoming` and `_endpoints_match`). `UVertex`/`DVertex` are deprecated aliases, by module `__getattr__`.
|
|
20
|
+
- **Records that must agree.** A vertex has `_edgelist` (leaving, or incident if undirected) and `_inlist` (entering, directed only); the graph has `_vertexlist`, `_vertexdict` (names) and `_edges` (an insertion-ordered dict). `g.validate()` checks them all; use it in tests of anything that edits the structure.
|
|
21
|
+
- **Two staleness flags, consumed independently.** `_connectivitychange` (components, reset by `_graphcolor`) and `_spatialchange` (the k-d tree). Set both wherever a vertex is added or removed; only connectivity cares about edges. Never share one flag.
|
|
22
|
+
- **`Vertex.__setattr__` is hot.** It exists to tell the graph that `coord` was assigned. Anything run per vertex (`Vertex.__init__`, `add_vertex`, components) writes `vertex.__dict__` directly to skip it; keep doing that, and declare new attributes at class level so mypy knows them.
|
|
23
|
+
- **`import pgraph` loads only numpy.** matplotlib, spatialmath and scipy are imported by the methods that need them (`plot`, `highlight_*`, `_edge_artist`, `build_kdtree`), and the classes that need matplotlib to be defined are in `_plot.py`. `tests/test_lazy_imports.py` runs a fresh interpreter to check it: do not add a module-level import of them.
|
|
24
|
+
- **Pickle and `copy` use a flat description** (`__getstate__`/`__setstate__`: vertices, edges by index, replayed through `_attach`); `subgraph` reuses it. Derived or transient state on a graph (the k-d tree) must be excluded there. A bare vertex or edge refuses to be pickled or copied.
|
|
25
|
+
- **Tests** are `unittest` classes in `tests/`, run with `pytest`; write expected values by hand or from an oracle in the test, never from the code under test. Import names explicitly (a star import is a Codacy `F405`); route `pickle` through one helper marked `# nosec B301 nosemgrep`. Codecov gates patch coverage, so every changed line needs a test. `mypy src` is a CI check.
|
|
26
|
+
- **Performance claims are measured.** `examples/benchmark.py` (`--md`, `--directed`) produces the README tables; regenerate them after a performance change and compare with `v0.6.5` (`git archive v0.6.5 src`), not with a development build.
|
|
27
|
+
- **Docs** are Sphinx (`make -C docs html`; `-n` for nitpicky) with `sphinx-pyrunblock` `.. runblock:: pycon` examples that execute at build. `docs/llms_intro.md` is the hand-written part of `llms.txt`: update it when the API changes; `docs/llms_txt.py` builds `llms.txt` and `llms-full.txt` from it and the toctrees (`make html llms`), and CI publishes them with the site.
|
|
28
|
+
- **Release.** Version is in `pyproject.toml`, history in `CHANGELOG.md`. Publishing is `.github/workflows/publish.yml` on a GitHub Release (trusted publishing, environment `pypi`): it refuses unless the tag equals the version and the commit is on `main`. Don't tag or upload by hand.
|
|
29
|
+
- `examples/london.json` is OpenStreetMap data (© OpenStreetMap contributors, ODbL); keep the attribution with it.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
1.0.0
|
|
2
|
+
|
|
3
|
+
First 1.0 release: six years of production use since the first commit in 2020
|
|
4
|
+
(including as a real dependency of both the Robotics Toolbox for Python and the
|
|
5
|
+
Machine Vision Toolbox for Python), and this cycle's type hints, documentation,
|
|
6
|
+
testing and bug-fixing pass, felt like the right point to commit to API
|
|
7
|
+
stability. This is the first release with a considered API, so it contains
|
|
8
|
+
several deliberate breaking changes, listed first with how to migrate.
|
|
9
|
+
|
|
10
|
+
Breaking changes (from 0.6.5)
|
|
11
|
+
|
|
12
|
+
- The `UGraph`/`DGraph` constructor arguments are keyword-only. 0.6.5 accepted
|
|
13
|
+
and silently ignored a first positional argument, so `UGraph(6)` looked as
|
|
14
|
+
though it set a dimension but did nothing. Now it is a `TypeError`: write
|
|
15
|
+
`UGraph(dim=6)`.
|
|
16
|
+
- `degree` of a `DGraph` is the graph-theory degree, in plus out, so a
|
|
17
|
+
self-loop counts 2 and the degrees sum to twice the number of edges. It used
|
|
18
|
+
to count only the edges leaving the vertex. This changes `vertex.degree`,
|
|
19
|
+
`graph.degree()` and `graph.connectivity()`, and `graph.average_degree()` is
|
|
20
|
+
now 2E/N for both kinds of graph (it was E/N for a `DGraph`). The previous
|
|
21
|
+
values are `vertex.out_degree`, `[v.out_degree for v in g]` and `g.ne / g.n`.
|
|
22
|
+
`laplacian()` is unchanged (out-degree minus adjacency). Undirected graphs
|
|
23
|
+
are unchanged.
|
|
24
|
+
- `graph.edges()` is a `list` in the order the edges were added. It was the
|
|
25
|
+
graph's own `set`, with no defined order, although documented as a list. Both
|
|
26
|
+
`graph.edges()` and `vertex.edges()` return a new list, so changing the result
|
|
27
|
+
cannot corrupt the graph. Use `set(g.edges())` for set operations.
|
|
28
|
+
- `Edge.v1` and `Edge.v2` are read-only, and so is `Vertex.label` (the graph
|
|
29
|
+
assigns it, and reading it now finds the components, where 0.6.5 gave `None`
|
|
30
|
+
until `nc` had been asked for). Assigning an endpoint of an edge that was in a graph left the
|
|
31
|
+
graph inconsistent and the edge impossible to remove. Give the vertices to
|
|
32
|
+
`Edge(v1, v2)` or connect the edge with `v1.connect(v2, edge=e)`.
|
|
33
|
+
- Assigning `Vertex.name` renames it in the graph (so `g[new]` works) and
|
|
34
|
+
refuses a name already in use or `None`.
|
|
35
|
+
- `add_vertex` raises `ValueError` for a name already used in the graph or a
|
|
36
|
+
vertex that already belongs to a graph, instead of silently shadowing it.
|
|
37
|
+
The default name `#N` is now the first free one, which could previously
|
|
38
|
+
repeat after a removal.
|
|
39
|
+
- There is one vertex class, `Vertex`: a vertex has no direction of its own, the
|
|
40
|
+
graph it belongs to decides how an edge is recorded at its ends. `UVertex`
|
|
41
|
+
and `DVertex` are deprecated names for it (below). A vertex given to
|
|
42
|
+
`add_vertex` is no longer checked against the kind of graph, because there is
|
|
43
|
+
no kind to mismatch. An override of the private `_attach`/`_detach` hooks
|
|
44
|
+
moves from a vertex subclass to a `UGraph`/`DGraph` subclass.
|
|
45
|
+
- The abstract base class `PGraph` is now `Graph`, and `Graph` and `Vertex` are
|
|
46
|
+
exported from the package. `Graph` is concrete: `Graph(directed=True)` is a
|
|
47
|
+
`DGraph` and `Graph()` a `UGraph`.
|
|
48
|
+
- `Adjacency()` treats a non-zero diagonal element as a self-loop.
|
|
49
|
+
- The `str` of an edge between vertices that are not in a graph shows `--`,
|
|
50
|
+
because there is no kind of graph to say whether it is directed.
|
|
51
|
+
|
|
52
|
+
Deprecated (still work, with a `DeprecationWarning`)
|
|
53
|
+
|
|
54
|
+
- `Laplacian()` is `laplacian()`: a capital initial denotes a class method that
|
|
55
|
+
builds a graph, like `Adjacency()` and `Dict()`.
|
|
56
|
+
- `UVertex` and `DVertex` are `Vertex`.
|
|
57
|
+
- `remove()` is `remove_vertex()` or `remove_edge()`, which say which they do.
|
|
58
|
+
- The options of `plot()` are named for what they are: `vopt` is `vertex_opt`,
|
|
59
|
+
`eopt` is `edge_opt`, `text` is `labels` and `text_opt`, `colorcomponents` is
|
|
60
|
+
`color_components` and `force2d` is `force_2d`. The positional order is
|
|
61
|
+
unchanged; giving both an old name and its new one is a `TypeError`.
|
|
62
|
+
|
|
63
|
+
Added
|
|
64
|
+
|
|
65
|
+
- `adjacent()` and `isadjacent()`, the graph-theory names, which have one
|
|
66
|
+
spelling. `neighbours()`, `neighbors()` and `isneighbour()` from 0.6.5 are
|
|
67
|
+
kept as aliases, with `isneighbor()` added, and none of them warn.
|
|
68
|
+
- `plot()` draws each edge once. A directed graph has arrowheads, which
|
|
69
|
+
`arrow_opt` styles; edges that join the same pair of vertices, in either
|
|
70
|
+
direction, are arcs that bow apart; and a loop is a circle at its vertex. In
|
|
71
|
+
0.6.5 a directed graph looked like an undirected one, parallel edges lay
|
|
72
|
+
exactly on top of each other and a loop was a line of no length. `plot()`
|
|
73
|
+
does the same in 3D, and `highlight_edge()` follows an arc.
|
|
74
|
+
- `Graph(directed=...)`, the `directed` property, and `vertex_class` and
|
|
75
|
+
`edge_class` constructor arguments, so that user subclasses of `Vertex` and
|
|
76
|
+
`Edge` are built by the graph.
|
|
77
|
+
- `add_edge()` accepts an `Edge`, and `connect()` an `edge=` argument.
|
|
78
|
+
- `edges_to(dest)` lists every edge from a vertex to another, so parallel edges
|
|
79
|
+
can be seen; `edgeto(dest)` is still the cheapest.
|
|
80
|
+
- Directed vertices record their incoming edges: `predecessors()`,
|
|
81
|
+
`successors()`, `in_edges()`, `out_edges()`, `in_degree` and `out_degree`.
|
|
82
|
+
- `export()` writes a graph as GraphViz dot, Mermaid, GraphML or ELK JSON, or as
|
|
83
|
+
an image rendered by the GraphViz executables, with the layout preserved. The
|
|
84
|
+
`pgraph.exporters` module can be extended with `register_exporter`.
|
|
85
|
+
- `validate()` reports every way in which a graph's records disagree, for tests
|
|
86
|
+
and debugging.
|
|
87
|
+
- A graph can be pickled, and `copy()` works for graphs of any size. `copy()`
|
|
88
|
+
raised `RecursionError` for a path of about 100 vertices.
|
|
89
|
+
- `iscyclic()`, `remove_edge()`, `remove_vertex()` and the `dim` argument, which
|
|
90
|
+
declares that every embedded vertex has a coordinate of exactly that length.
|
|
91
|
+
- Self-loops and parallel edges are supported throughout: adjacency, incidence,
|
|
92
|
+
degree and Laplacian matrices, and planning, which takes the cheapest of
|
|
93
|
+
parallel edges.
|
|
94
|
+
- `SE2Metric(weight)` is the `"SE2"` metric with a weight on the angle, the
|
|
95
|
+
length that a radian is worth, so that position and orientation need not
|
|
96
|
+
count equally. `"SE2"` is `SE2Metric()`, a weight of 1, so existing results
|
|
97
|
+
are unchanged. It can be the metric or the heuristic of a graph and is
|
|
98
|
+
pickled with its weight.
|
|
99
|
+
- Spatial queries on an embedded graph: `coordinates()` is the matrix of vertex
|
|
100
|
+
coordinates, a column per vertex; `within(box)` the vertices inside a box;
|
|
101
|
+
`near(point, radius)` those within a distance, nearest first; and
|
|
102
|
+
`subgraph(vertices)` the graph that a set of vertices induces, given as
|
|
103
|
+
vertices, names or a boolean mask, as a new graph that shares nothing with
|
|
104
|
+
the original.
|
|
105
|
+
- `build_kdtree()` makes an optional k-d tree that `closest()` and `near()` use
|
|
106
|
+
for the built-in metrics, `SE2Metric` included. It needs scipy, which is
|
|
107
|
+
imported only then and is not a dependency; and it is dropped whenever a
|
|
108
|
+
vertex is added or removed or a coordinate is assigned, so a stale one is
|
|
109
|
+
never used.
|
|
110
|
+
- `validate()` also checks that coordinates are finite vectors of one length,
|
|
111
|
+
and with `embedded=True` that every vertex has one.
|
|
112
|
+
|
|
113
|
+
Faster
|
|
114
|
+
|
|
115
|
+
- `path_BFS`, `path_UCS` and `path_Astar` scaled badly: they searched lists with
|
|
116
|
+
`in`, and took the minimum of the whole frontier at every step. Results and
|
|
117
|
+
the verbose output are identical. On a random graph of 10,000 vertices and
|
|
118
|
+
30,000 edges, the mean of six random queries is 3 ms for breadth-first search
|
|
119
|
+
where 0.6.5 took 172 ms, 18 ms for uniform cost where it took 1,500 ms, and
|
|
120
|
+
13 ms for A* where it took 410 ms.
|
|
121
|
+
- The components are found by a union-find over the edges, 4 ms for that graph
|
|
122
|
+
where 0.6.5 took 15 ms after a change.
|
|
123
|
+
- Creating a vertex is faster: `Vertex(coord)` takes 0.35 µs where 0.6.5 took
|
|
124
|
+
1.5 µs, and `g.add_vertex(coord)` 1.7 µs where it took 2.2 µs. Making the
|
|
125
|
+
coordinate with `np.r_` was most of the cost.
|
|
126
|
+
- `import pgraph` takes 0.04 s where it took 0.6 s, and loads neither
|
|
127
|
+
matplotlib, spatialmath nor scipy: they are imported by the methods that plot
|
|
128
|
+
or build a spatial index. A script that only builds graphs and plans paths no
|
|
129
|
+
longer pays for them.
|
|
130
|
+
- `str(g)` separates thousands: `DGraph: 4,881 vertices, 8,392 edges, 14
|
|
131
|
+
components`.
|
|
132
|
+
|
|
133
|
+
Fixed
|
|
134
|
+
|
|
135
|
+
- `Dict()` always created undirected vertices, even for a `DGraph`; `connect()`
|
|
136
|
+
joined vertices of two different graphs, and now also validates the
|
|
137
|
+
endpoints of a supplied edge; `path_BFS`/`path_UCS`/`path_Astar` checked the
|
|
138
|
+
type of the start vertex twice and never the goal; and `distance()` and the
|
|
139
|
+
planners packed `nan` or `None` into their results for edges with no cost
|
|
140
|
+
instead of raising.
|
|
141
|
+
- `nc` never returned for many directed graphs, the smallest a 4-cycle, and
|
|
142
|
+
neither did `component()`, `samecomponent()` or `plot()`, which ask for it.
|
|
143
|
+
Components of a directed graph are weakly connected, as they were meant to
|
|
144
|
+
be, and are numbered from 0 by the first vertex of each, for both kinds of
|
|
145
|
+
graph. They are also no longer found again by every call once the graph has
|
|
146
|
+
changed.
|
|
147
|
+
- `Edge.vertices()` raised `DeprecationWarning` as an exception; it now warns
|
|
148
|
+
and returns the endpoints, as `Edge.endpoints` does.
|
|
149
|
+
- `incidence()` returned the wrong matrix for every graph with an edge: a loop
|
|
150
|
+
variable overwrote the vertex index, so edges were marked in the wrong rows,
|
|
151
|
+
and for a directed graph the vertex at the end of an edge was not marked at
|
|
152
|
+
all. The columns follow `edges()`, which is now in the order the edges were
|
|
153
|
+
added, where 0.6.5 used the order of a set.
|
|
154
|
+
- `remove()` crashed for every directed edge; `dotfile()` closed `sys.stdout`
|
|
155
|
+
when called with its default arguments; and several methods raised a bare
|
|
156
|
+
`AttributeError` or `TypeError` instead of a clear message for a vertex with
|
|
157
|
+
no coordinate or no graph.
|
|
158
|
+
- `highlight_edge()`, and so `highlight_path()`, raised `AttributeError` for a
|
|
159
|
+
graph of three dimensions, because it drew a flat arrow in the 3D axes (0.6.5
|
|
160
|
+
drew lines), and `highlight_vertex()` put its marker at z = 0. They draw in
|
|
161
|
+
the dimension of the axes, so a 3D graph plotted with `force_2d` is
|
|
162
|
+
highlighted in 2D.
|
|
163
|
+
- Removing a vertex left its `_graph` pointing at the graph; renaming a vertex
|
|
164
|
+
left the name table stale; `g[x]` accepted any type.
|
|
165
|
+
|
|
166
|
+
Known limitations
|
|
167
|
+
|
|
168
|
+
- A graph whose metric is a `lambda` or a locally defined function can be copied
|
|
169
|
+
but not pickled; the built-in `"L1"`, `"L2"` and `"SE2"` and module-level
|
|
170
|
+
functions can be.
|
|
171
|
+
|
|
172
|
+
Build, tests and documentation
|
|
173
|
+
|
|
174
|
+
- Modern type hints and reST docstrings throughout, most with a runnable
|
|
175
|
+
example, and `mypy` is a CI check.
|
|
176
|
+
- The documentation is organized by task, with the data structures explained and
|
|
177
|
+
a policy page on object lifecycle, and builds with `sphinx-pyrunblock` (which
|
|
178
|
+
replaces the unmaintained `sphinx-autorun`) and the matplotlib plot directive.
|
|
179
|
+
- The source distribution no longer contains a stale copy of the source
|
|
180
|
+
(`build/lib`, left by a setuptools build before the move to the `src` layout),
|
|
181
|
+
nor the `RELEASE` file, which held `0.6.1` and which nothing read.
|
|
182
|
+
- Python 3.9 to 3.14 are tested, the unused `scipy` dependency is dropped, and
|
|
183
|
+
PyPI publishing is automated through GitHub Releases and trusted publishing.
|
|
184
|
+
The workflow checks that the tag is the version in `pyproject.toml` and that
|
|
185
|
+
the tagged commit is on `main`, and installs the built wheel and runs a path
|
|
186
|
+
query before it uploads.
|
|
187
|
+
- The documentation site is deployed with the GitHub Pages actions. It had not
|
|
188
|
+
been updated since January 2025, because the CI pushed to a branch that Pages
|
|
189
|
+
no longer served.
|
|
190
|
+
- `llms.txt` and `llms-full.txt` are published with the documentation, for AI
|
|
191
|
+
assistants: a short introduction with a quick start and common mistakes, and the
|
|
192
|
+
whole documentation as one text file. `AGENTS.md` describes the source for
|
|
193
|
+
assistants that work on it.
|
|
194
|
+
- Two notebooks in `examples/`: `roads.ipynb`, the Queensland example, and
|
|
195
|
+
`london.ipynb`, the road network of central London from OpenStreetMap as a
|
|
196
|
+
directed graph with one-way streets, planned through with A*. There is also
|
|
197
|
+
a benchmark, `examples/benchmark.py`, and the README has its results and an
|
|
198
|
+
installation section.
|
|
199
|
+
- The Queensland figure in the README shows the shortest route. It had been
|
|
200
|
+
drawn with coordinates in metres and distances in kilometres, which made the
|
|
201
|
+
A* heuristic far too large, so the route it found, 1,759 km, was not the
|
|
202
|
+
shortest, 1,659 km.
|
|
203
|
+
|
|
204
|
+
0.6.3
|
|
205
|
+
|
|
206
|
+
- runs with Numpy2.x
|
|
207
|
+
- changed build system to .toml file
|
|
208
|
+
- misc doco and changes
|
|
209
|
+
- migrate package to src layout (pgraph under src/pgraph)
|
|
210
|
+
- switch build backend to hatchling
|
|
211
|
+
- update CI workflow for main branch and docs publish to gh-pages
|
|
212
|
+
- update repository links and badges from master to main
|
|
@@ -9,8 +9,8 @@ help:
|
|
|
9
9
|
@echo " make docs - build Sphinx documentation"
|
|
10
10
|
@echo " make docupdate - upload Sphinx documentation to GitHub pages"
|
|
11
11
|
@echo " make dist - build dist files"
|
|
12
|
-
@echo " make upload - upload to PyPI"
|
|
13
12
|
@echo " make clean - remove dist and docs build files"
|
|
13
|
+
@echo " (PyPI publish is via GitHub Releases, see .github/workflows/publish.yml)"
|
|
14
14
|
@echo " make help - this message$(BLACK)"
|
|
15
15
|
|
|
16
16
|
test:
|
|
@@ -34,17 +34,6 @@ dist: .FORCE
|
|
|
34
34
|
#$(MAKE) test
|
|
35
35
|
python -m build
|
|
36
36
|
|
|
37
|
-
pypi: .FORCE
|
|
38
|
-
validate-pyproject pyproject.toml
|
|
39
|
-
@if ! git diff --quiet || ! git diff --cached --quiet; then \
|
|
40
|
-
echo "Error: uncommitted changes present, aborting upload"; exit 1; \
|
|
41
|
-
fi
|
|
42
|
-
$(eval VERSION := $(shell grep '^version' pyproject.toml | sed 's/version = "\(.*\)"/\1/'))
|
|
43
|
-
python -m build
|
|
44
|
-
twine upload dist/*
|
|
45
|
-
git tag v$(VERSION)
|
|
46
|
-
git push origin v$(VERSION)
|
|
47
|
-
|
|
48
37
|
clean: .FORCE
|
|
49
38
|
(cd docs; make clean)
|
|
50
39
|
-rm -r *.egg-info
|