Skip to content

Commit 13cb229

Browse files
authored
Merge pull request #155 from benchmark-urbanism/dev
## Summary - Adaptive per-distance sampling for centrality (experimental): `sample=True` with Hoeffding-bound calibrated probabilities per distance threshold - Convenience wrappers: `closeness_shortest`, `closeness_simplest`, `betweenness_shortest`, `betweenness_simplest` - OD-matrix betweenness: `build_od_matrix`, `betweenness_od` - QGIS plugin for network centrality analysis with custom parameter dialog - Graph mutation: `set_node_live`, `remove_street_node`, `remove_street_edge` - Brandes multi-predecessor betweenness with configurable tolerance - New plugin page, sampling guide, and visibility module docs - Regenerated API docs reflecting all signature changes - Updated Python version range to 3.10-3.13 - Remove paper build artifacts from tracking ## Breaking changes - Rename `jitter_scale` parameter to `tolerance` in `node_centrality_shortest`, `node_centrality_simplest`, and `segment_centrality` - Rename Rust methods: `local_node_centrality_shortest` → `centrality_shortest`, `local_node_centrality_simplest` → `centrality_simplest`, `local_segment_centrality` → `segment_centrality` ## Acknowledgements Thanks to Robin Lovelace (@Robinlovelace) for initiating the adaptive sampling idea and for contributing to its development.
2 parents a896027 + 8c7150f commit 13cb229

File tree

95 files changed

+16720
-1808
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+16720
-1808
lines changed

.claude/settings.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(export PATH=\"$HOME/.cargo/bin:$HOME/.local/bin:$PATH\")",
5+
"Bash(/Users/gareth/.local/bin/uv run maturin develop:*)",
6+
"Bash(export PATH=\"$HOME/.cargo/bin:/usr/bin:/bin:/usr/local/bin:$PATH\")",
7+
"Bash(maturin develop:*)",
8+
"Bash(source:*)",
9+
"Bash(HOME=/Users/gareth)",
10+
"Bash(export:*)",
11+
"Bash(. \"$HOME/.cargo/env\")",
12+
"Bash(echo:*)",
13+
"Read(//Users/gareth/.cargo/**)",
14+
"Bash(. /Users/gareth/.cargo/env)",
15+
"Bash(grep:*)",
16+
"Bash(.venv/bin/pytest tests/ --ignore=tests/metrics/test_visibility.py -x)",
17+
"Bash(.venv/bin/pytest tests/rustalgos/test_centrality.py tests/metrics/test_networks.py -x -v)",
18+
"Bash(.venv/bin/pytest tests/ --ignore=tests/metrics/test_visibility.py --ignore=tests/tools/test_io.py -x)",
19+
"Bash(.venv/bin/pytest:*)"
20+
]
21+
}
22+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: build qgis plugin zip
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
tags:
7+
- "*"
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
build-qgis-plugin:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: "3.12"
23+
24+
- name: Build QGIS plugin zip
25+
run: python qgis_plugin/build_plugin.py
26+
27+
- name: Verify plugin zip contents
28+
run: |
29+
python - <<'PY'
30+
import zipfile
31+
from pathlib import Path
32+
33+
zips = sorted(Path("qgis_plugin").glob("cityseer-qgis-*.zip"))
34+
if not zips:
35+
raise SystemExit("No QGIS plugin zip was generated.")
36+
zip_path = zips[-1]
37+
required = {
38+
"cityseer_qgis/__init__.py",
39+
"cityseer_qgis/provider.py",
40+
"cityseer_qgis/metadata.txt",
41+
"cityseer_qgis/LICENSE",
42+
}
43+
with zipfile.ZipFile(zip_path) as zf:
44+
names = set(zf.namelist())
45+
missing = sorted(required - names)
46+
if missing:
47+
raise SystemExit(f"Missing files in {zip_path.name}: {missing}")
48+
print(f"Verified {zip_path.name}")
49+
PY
50+
51+
- name: Upload QGIS plugin zip artifact
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: qgis-plugin-zip
55+
path: qgis_plugin/cityseer-qgis-*.zip
56+
if-no-files-found: error
57+
58+
- name: Attach zip to GitHub release (tags)
59+
if: startsWith(github.ref, 'refs/tags/')
60+
uses: softprops/action-gh-release@v2
61+
with:
62+
files: qgis_plugin/cityseer-qgis-*.zip
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Generate Changelog
2+
on:
3+
push:
4+
tags:
5+
- "v*"
6+
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: "Tag to generate changelog for (leave empty for latest)"
10+
required: false
11+
type: string
12+
permissions:
13+
contents: write
14+
jobs:
15+
changelog:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Determine tag
24+
id: tag
25+
run: |
26+
if [ -n "${{ github.event.inputs.tag }}" ]; then
27+
echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
28+
else
29+
echo "tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT
30+
fi
31+
32+
- name: Generate changelog
33+
uses: orhun/git-cliff-action@v4
34+
id: cliff
35+
with:
36+
config: cliff.toml
37+
args: --latest --strip header
38+
env:
39+
OUTPUT: CHANGES.md
40+
41+
- name: Create or update GitHub Release
42+
uses: softprops/action-gh-release@v2
43+
with:
44+
tag_name: ${{ steps.tag.outputs.tag }}
45+
body_path: CHANGES.md
46+
draft: false
47+
prerelease: ${{ contains(steps.tag.outputs.tag, 'a') || contains(steps.tag.outputs.tag, 'b') }}

.github/workflows/publish_package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: ["3.11", "3.12", "3.13"]
13+
python-version: ["3.10", "3.11", "3.12", "3.13"]
1414
steps:
1515
- name: Checkout code
1616
uses: actions/checkout@v4

.github/workflows/publish_package_dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ["3.11", "3.12", "3.13"]
14+
python-version: ["3.10", "3.11", "3.12", "3.13"]
1515
steps:
1616
- name: Checkout code
1717
uses: actions/checkout@v4

.gitignore

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# mac
22
.DS_Store
3+
CLAUDE.md
34

45
# astro
56
.astro
67

7-
# cache
8-
*cache*
8+
# cache directories (trailing slash = directories only)
9+
*cache*/
910

1011
# code profiling
1112
.prof
@@ -123,3 +124,24 @@ bin/
123124
include/
124125
man/
125126

127+
# QGIS plugin build artifacts
128+
qgis_plugin/*.zip
129+
130+
# LaTeX generated files
131+
*.aux
132+
*.bbl
133+
*.blg
134+
*.fdb_latexmk
135+
*.fls
136+
*.lof
137+
*.lot
138+
*.out
139+
*.toc
140+
*.synctex.gz
141+
missfont.log
142+
143+
# Paper build artifacts (auto-generated by analysis scripts)
144+
analysis/sampling/paper/main.pdf
145+
analysis/sampling/paper/figures/
146+
analysis/sampling/paper/tables/
147+

.vscode/settings.json

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,62 @@
3131
"editor.defaultFormatter": "rust-lang.rust-analyzer",
3232
"editor.formatOnSave": true
3333
},
34-
"python.testing.pytestArgs": [
35-
"tests"
36-
],
34+
"python.testing.pytestArgs": ["tests"],
3735
"python.testing.unittestEnabled": false,
3836
"python.testing.pytestEnabled": true,
3937
"files.readonlyInclude": {
4038
"**/.cargo/registry/src/**/*.rs": true,
41-
"**/lib/rustlib/src/rust/library/**/*.rs": true,
39+
"**/lib/rustlib/src/rust/library/**/*.rs": true
40+
},
41+
"latex-workshop.latex.tools": [
42+
{
43+
"name": "pdflatex",
44+
"command": "/Library/TeX/texbin/pdflatex",
45+
"args": [
46+
"-synctex=1",
47+
"-interaction=nonstopmode",
48+
"-file-line-error",
49+
"%DOC%"
50+
]
51+
},
52+
{
53+
"name": "latexmk",
54+
"command": "/Library/TeX/texbin/latexmk",
55+
"args": [
56+
"-pdf",
57+
"-synctex=1",
58+
"-interaction=nonstopmode",
59+
"-file-line-error",
60+
"%DOC%"
61+
],
62+
"env": {
63+
"PATH": "/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin"
64+
}
65+
},
66+
{
67+
"name": "bibtex",
68+
"command": "/Library/TeX/texbin/bibtex",
69+
"args": ["%DOCFILE%"]
70+
}
71+
],
72+
"latex-workshop.latex.recipes": [
73+
{
74+
"name": "latexmk",
75+
"tools": ["latexmk"]
76+
},
77+
{
78+
"name": "pdflatex -> bibtex -> pdflatex * 2",
79+
"tools": ["pdflatex", "bibtex", "pdflatex", "pdflatex"]
80+
}
81+
],
82+
"latex-workshop.latex.autoBuild.run": "onSave",
83+
"latex-workshop.latex.autoClean.run": "never",
84+
"latex-workshop.latex.clean.subfolder.enabled": true,
85+
"latex-workshop.view.pdf.viewer": "tab",
86+
"latex-workshop.synctex.afterBuild.enabled": true,
87+
"latex-workshop.formatting.latex": "tex-fmt",
88+
"files.watcherExclude": {
89+
"**/temp/**": true,
90+
"**/.cache/**": true
4291
}
4392
}

0 commit comments

Comments
 (0)