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.
Files changed (113) hide show
  1. pgraph_python-1.0.0/.codacy.yml +7 -0
  2. pgraph_python-1.0.0/.github/dependabot.yml +8 -0
  3. pgraph_python-1.0.0/.github/workflows/ci.yml +121 -0
  4. pgraph_python-1.0.0/.github/workflows/dependabot-auto-merge.yml +25 -0
  5. pgraph_python-1.0.0/.github/workflows/publish.yml +115 -0
  6. pgraph_python-1.0.0/AGENTS.md +29 -0
  7. pgraph_python-1.0.0/CHANGELOG.md +212 -0
  8. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/Makefile +1 -12
  9. pgraph_python-1.0.0/PKG-INFO +277 -0
  10. pgraph_python-1.0.0/README.md +236 -0
  11. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/Makefile +5 -1
  12. pgraph_python-1.0.0/docs/figs/datastructures.ezdraw +3172 -0
  13. pgraph_python-1.0.0/docs/figs/datastructures.svg +13239 -0
  14. pgraph_python-1.0.0/docs/figs/london_route.png +0 -0
  15. pgraph_python-1.0.0/docs/figs/pgraph_logo.png +0 -0
  16. pgraph_python-1.0.0/docs/figs/pgraph_logo.py +161 -0
  17. pgraph_python-1.0.0/docs/llms_intro.md +43 -0
  18. pgraph_python-1.0.0/docs/llms_txt.py +170 -0
  19. pgraph_python-1.0.0/docs/source/_static/custom.css +15 -0
  20. pgraph_python-1.0.0/docs/source/_templates/footer.html +13 -0
  21. pgraph_python-1.0.0/docs/source/analysis.rst +203 -0
  22. pgraph_python-1.0.0/docs/source/api.rst +9 -0
  23. pgraph_python-1.0.0/docs/source/common.rst +37 -0
  24. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/source/conf.py +31 -4
  25. pgraph_python-1.0.0/docs/source/construction.rst +76 -0
  26. pgraph_python-1.0.0/docs/source/edges_vertices.rst +249 -0
  27. pgraph_python-1.0.0/docs/source/export.rst +35 -0
  28. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/source/index.rst +4 -11
  29. pgraph_python-1.0.0/docs/source/intro.rst +304 -0
  30. pgraph_python-1.0.0/docs/source/modification.rst +55 -0
  31. pgraph_python-1.0.0/docs/source/planning.rst +20 -0
  32. pgraph_python-1.0.0/docs/source/policy.rst +94 -0
  33. pgraph_python-1.0.0/docs/source/representations.rst +25 -0
  34. pgraph_python-1.0.0/docs/source/visualization.rst +52 -0
  35. pgraph_python-1.0.0/examples/README.md +37 -0
  36. pgraph_python-1.0.0/examples/benchmark.py +344 -0
  37. pgraph_python-1.0.0/examples/london.ipynb +1094 -0
  38. pgraph_python-1.0.0/examples/london.json +20340 -0
  39. pgraph_python-1.0.0/examples/osm.py +248 -0
  40. pgraph_python-1.0.0/examples/roads.ipynb +461 -0
  41. pgraph_python-1.0.0/examples/roads.png +0 -0
  42. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/pyproject.toml +15 -7
  43. pgraph_python-1.0.0/src/pgraph/PGraph.py +5343 -0
  44. pgraph_python-1.0.0/src/pgraph/__init__.py +33 -0
  45. pgraph_python-1.0.0/src/pgraph/_plot.py +70 -0
  46. pgraph_python-1.0.0/src/pgraph/exporters.py +502 -0
  47. pgraph_python-1.0.0/tests/test_add_edge_edge.py +109 -0
  48. pgraph_python-1.0.0/tests/test_add_vertex_names.py +116 -0
  49. pgraph_python-1.0.0/tests/test_adjacency_loops.py +89 -0
  50. pgraph_python-1.0.0/tests/test_adjacent.py +106 -0
  51. pgraph_python-1.0.0/tests/test_attribute_hygiene.py +170 -0
  52. pgraph_python-1.0.0/tests/test_components.py +340 -0
  53. pgraph_python-1.0.0/tests/test_connect_edge.py +96 -0
  54. pgraph_python-1.0.0/tests/test_constructor_keywords.py +40 -0
  55. pgraph_python-1.0.0/tests/test_detach_hook.py +131 -0
  56. pgraph_python-1.0.0/tests/test_edge_endpoints.py +124 -0
  57. pgraph_python-1.0.0/tests/test_edges_list.py +97 -0
  58. pgraph_python-1.0.0/tests/test_edges_to.py +103 -0
  59. pgraph_python-1.0.0/tests/test_export.py +523 -0
  60. pgraph_python-1.0.0/tests/test_getitem.py +68 -0
  61. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/tests/test_graph.py +363 -65
  62. pgraph_python-1.0.0/tests/test_graph_classes.py +176 -0
  63. pgraph_python-1.0.0/tests/test_highlight_3d.py +128 -0
  64. pgraph_python-1.0.0/tests/test_in_edges.py +203 -0
  65. pgraph_python-1.0.0/tests/test_incidence.py +199 -0
  66. pgraph_python-1.0.0/tests/test_laplacian_name.py +43 -0
  67. pgraph_python-1.0.0/tests/test_lazy_imports.py +89 -0
  68. pgraph_python-1.0.0/tests/test_multigraph.py +303 -0
  69. pgraph_python-1.0.0/tests/test_pickle.py +256 -0
  70. pgraph_python-1.0.0/tests/test_planner_choices.py +231 -0
  71. pgraph_python-1.0.0/tests/test_plot_args.py +113 -0
  72. pgraph_python-1.0.0/tests/test_plot_arrows.py +392 -0
  73. pgraph_python-1.0.0/tests/test_se2_metric.py +139 -0
  74. pgraph_python-1.0.0/tests/test_spatial_index.py +414 -0
  75. pgraph_python-1.0.0/tests/test_subgraph.py +380 -0
  76. pgraph_python-1.0.0/tests/test_unified_graph.py +265 -0
  77. pgraph_python-1.0.0/tests/test_validate.py +235 -0
  78. pgraph_python-1.0.0/tests/test_vertex_hook.py +154 -0
  79. pgraph_python-1.0.0/tests/test_vertex_init.py +165 -0
  80. pgraph_python-0.6.4/.github/workflows/ci.yml +0 -77
  81. pgraph_python-0.6.4/CHANGELOG.md +0 -9
  82. pgraph_python-0.6.4/PKG-INFO +0 -176
  83. pgraph_python-0.6.4/README.md +0 -138
  84. pgraph_python-0.6.4/RELEASE +0 -1
  85. pgraph_python-0.6.4/build/lib/pgraph/PGraph.py +0 -1918
  86. pgraph_python-0.6.4/build/lib/pgraph/__init__.py +0 -1
  87. pgraph_python-0.6.4/docs/source/PGraph.rst +0 -7
  88. pgraph_python-0.6.4/docs/source/datastructures.ezdraw +0 -2353
  89. pgraph_python-0.6.4/docs/source/dgraph.rst +0 -9
  90. pgraph_python-0.6.4/docs/source/directed.rst +0 -9
  91. pgraph_python-0.6.4/docs/source/dvertex.rst +0 -8
  92. pgraph_python-0.6.4/docs/source/edge.rst +0 -8
  93. pgraph_python-0.6.4/docs/source/matrices.rst +0 -7
  94. pgraph_python-0.6.4/docs/source/modules.rst +0 -7
  95. pgraph_python-0.6.4/docs/source/planning.rst +0 -7
  96. pgraph_python-0.6.4/docs/source/ugraph.rst +0 -9
  97. pgraph_python-0.6.4/docs/source/undirected.rst +0 -9
  98. pgraph_python-0.6.4/docs/source/uvertex.rst +0 -8
  99. pgraph_python-0.6.4/examples/README.md +0 -8
  100. pgraph_python-0.6.4/examples/roads.png +0 -0
  101. pgraph_python-0.6.4/src/pgraph/PGraph.py +0 -1912
  102. pgraph_python-0.6.4/src/pgraph/__init__.py +0 -1
  103. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/.gitignore +0 -0
  104. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/LICENSE +0 -0
  105. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/make.bat +0 -0
  106. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/docs/source/datastructures.png +0 -0
  107. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/create_json.py +0 -0
  108. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/places.json +0 -0
  109. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/queensland.json +0 -0
  110. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/roads.py +0 -0
  111. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/examples/routes.json +0 -0
  112. {pgraph_python-0.6.4 → pgraph_python-1.0.0}/posegraph.py +0 -0
  113. {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,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "monthly"
7
+ labels:
8
+ - "dependencies"
@@ -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