Conversation
|
Visit the preview URL for this PR (updated for commit 6490c73): https://cityseer-api-docs--pr171-dev-e2fmbz2l.web.app (expires Wed, 24 Jun 2026 07:32:38 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 470ca74c3557f8695e6e641c5f19c203859880a1 |
Accept either "geometry" or "geom" edge attributes in from_nx, and return the original graph structure (with cc_ columns added) from to_nx when the network was built via from_nx. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pulls centrality accumulation fixes from master: - Source-based accumulation when not sampling (prevents edge roll-off) - Target-based with dead buffer sources when sampling - Directional Dijkstra (downstream/upstream) matching accumulation mode - Dead-to-dead betweenness exclusion and dead target skip for closeness Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds directed (one-way) routing via CityNetwork.from_geopandas(directed=True) with a boolean oneway column, CityNetwork.from_nx() auto-detecting MultiDiGraph, and io.network_structure_from_nx() for the low-level API. Each directed edge becomes its own dual node marked one-way in its coordinate direction. The Rust Dijkstra already follows Direction::Outgoing, so no algorithm changes are needed — directionality is controlled entirely by which edges the Python-side graph construction adds. The graphs module simplification pipeline remains undirected-only; from_osm uses that pipeline and stays undirected. Includes strict oneway column validation (rejects NaN, non-boolean), directed update() that rebuilds directions from incoming GeoDataFrame, to_nx() raising NotImplementedError for directed networks without a source graph, save/load roundtrip, and 16 new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New `decay_fn` parameter (expression string using variable `p`) replaces `betas`, `min_threshold_wt`, and `spatial_tolerance` on centrality, accessibility, mixed-use, and stats functions - New `cityseer.decay` module with helpers: exponential, linear, flat, gaussian, logistic - Rename node_beta → node_decay, node_betweenness_beta → node_betweenness_decay - Unify weighted/unweighted metrics into single decay-controlled output (remove _wt/_nw column suffixes) - Add meval crate for Rust-side expression parsing - Update QGIS plugin for new API (centrality, accessibility, stats) - Add comprehensive docs: decay functions, column naming conventions, output reference tables, CityNetwork examples - Segment centrality retains old betas API (analytical integral) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oss-links - New /guide page: installation, core concepts, CityNetwork API, centrality, decay functions, land-use analysis, directed graphs, elevation, column naming, performance guidance, adaptive sampling — with cross-links to 20+ cityseer-examples recipe notebooks - Trim intro.md to focused landing page (321 → 48 lines), pointing to guide - Add /guide to navigation sidebar - Add module docstrings to layers.py and network.py - Add examples to node_centrality_shortest, node_centrality_simplest, nx_remove_filler_nodes - Add cityseer-examples cross-links to compute_accessibilities, compute_mixed_uses, compute_stats, street_continuity, CityNetwork class - Fix broken links: /config#build-od-matrix, /guide#graph-cleaning - Fix incomplete docstring line in layers.py build_data_map Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix "composes with" → "is applied on top of" (elevation section) - Tighten street continuity description - Replace "unbiased" with precise statistical language in sampling section - Note that both centrality_shortest and centrality_simplest support sampling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Page <title> now shows "cityseer — network" instead of just "/api/network" - Add og:title, og:type, og:url meta tags for social sharing - robots.txt and sitemap already in place Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SEO improvements:
- Per-page meta descriptions for all 20 documentation pages
- Open Graph tags (og:title, og:description, og:type, og:url, og:image,
og:site_name, og:locale) on every page
- Twitter Card tags (summary card with image) on every page
- JSON-LD structured data (SoftwareApplication schema) on every page
- Canonical URLs on every page
- Descriptive page titles ("cityseer — guide" not just "/guide")
- Fix EPSG codes being eaten by remark-directive (use backtick formatting)
Astro 5 config cleanup:
- Remove deprecated markdown.drafts option (removed in Astro 3)
- Remove redundant remarkGfm plugin (built-in since Astro 5)
- Add markdown.smartypants: false to prevent double-processing with
remark-smartypants plugin (which uses custom dash options)
- Add markdown.gfm: true for clarity
Dependency cleanup:
- Remove @astrojs/markdown-component (dead code, Astro 1.x migration aid)
- Remove autoprefixer (unused, Tailwind v4 handles prefixing)
- Remove pug-plain-loader (Webpack-era vestige, Vite uses pug directly)
- Add @astrojs/check and typescript (required for astro check script)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ity functions Replace the separate segment_centrality code path (edge-based continuous integrals, dedicated Dijkstra, CentralitySegmentResult) with a segment_weighted boolean on the existing centrality functions. On dual graphs, this sets node weights to primal edge lengths so that closeness measures reflect total reachable street length and betweenness weights pairs by both endpoint segment lengths. - Rename node_centrality_shortest → centrality_shortest - Rename node_centrality_simplest → centrality_simplest - Add segment_weighted param (Rust + Python) with _SegmentWeightContext - Add set_node_weight method on NetworkStructure - Replace pair_distances_betas_time with pair_distances_and_time - Clean up betas machinery from Python callers and log_thresholds - Remove dead code: EdgeVisit, dijkstra_tree_segment, origin_seg/last_seg, unchecked edge helpers, CentralitySegmentResult - Update all docs, tests, type stubs, and references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace boolean flags (compute_closeness, compute_betweenness) and hardcoded
metric accumulation with user-defined {name: expression} dicts evaluated via
meval. Centrality metrics are now specified using variables c (cost) and
p (normalised progress), making them user-extensible without Rust changes.
- Add validate_metric_expr/parse_metric_expr using meval bind2("c", "p")
- Replace 3 result types with generic CentralityResult with metrics dict
- Generalise brandes_backprop from 2 fixed channels to N
- Remove decay_fn, angular_scaling_unit, farness_scaling_offset params
- Add safe AST-based postprocess evaluator (no eval())
- Update QGIS plugin for new API
- 4.25.0b13
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-node `weight` now applies gravity-style weighting consistently across shortest- and simplest-path centrality, in both full and sampled modes: - Closeness weights each reachable node by its destination weight (A(N) = sum_j w_j * f(d(N,j))) instead of rescaling the node's own score. - Betweenness weights each O-D pair by the product of its endpoint weights. - Full and sampled paths now agree under non-uniform weights (previously the full path weighted by source/self while sampling weighted by destination). - Fix cycles_wt 0/0 -> NaN for zero-weight sources under sampling by deriving the IPW factor directly from sample_source_weight. - Remove the now-redundant Rust `segment_weighted` flag and dead `n_live` field; node weighting is applied uniformly and the Python `_SegmentWeightContext` remains the segment-length preset. Document the node `weight` mechanism in the guide (gravity semantics; land-use aggregations remain intentionally unweighted) and add regression tests for gravity closeness, full==sampled consistency, betweenness product weighting, and zero-weight NaN safety. Also resolve pre-existing `ty` type-check errors (plot/graphs/util/network) so `verify_project` passes cleanly. Bump version to 4.25.0b16. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swaps the unmaintained meval (and its future-incompat nom 1.2.4) for the maintained exmex as the c/p expression evaluator. No native fast-path: a release benchmark showed expression eval is negligible end-to-end (a heavy interpreted expr costs 1.03x a trivial one — Dijkstra/Brandes traversal dominates), so hard-coded paths would be complexity for no measurable gain. The earlier "eval is ~24% of runtime" figure was a debug-build artifact. - common.rs: exmex evaluator with c/p variable validation (exmex exposes n_vars but not names, so a small standalone-identifier scan detects c/p); custom sqrt/abs/floor/ceil/round/signum ops to match the prior surface. - decay.py: linear() -> "1 - p" (exmex has no comma-functions like max(); output is clamped anyway); gaussian() parenthesises the squared term to avoid exmex's unary-minus/^ precedence flipping the sign. - nom is gone from the dependency tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
generate_docs.py had an unfinished `raise NotImplementedError("Deprecation
not implemented.")` for any docstring carrying a deprecation. A freshly-synced
(newer) docstring-parser in CI detects the shims' deprecation and tripped it,
breaking the docs pipeline. Now renders a "### Deprecated" section.
- generate_docs.py: render deprecation.version/description.
- networks.py: node_centrality_shortest/simplest + segment_centrality use proper
`.. deprecated:: 4.25` directives (idiomatic, now handled), regenerated docs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Followup to the meval->exmex swap. exmex's DefaultOpsFactory already provides sqrt/floor/ceil/signum (my custom ops duplicated them) but NOT the names ln, log10, abs, round that meval supported. Restores those as custom unary ops so existing expressions using ln/log10/abs keep working; drops the duplicates. min/max remain unsupported (exmex has no comma-function syntax) - documented. - layers.py: corrected the decay_fn function list (no min/max; note -((x)^2)); parenthesised the Gaussian docstring example to match decay.gaussian(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… roll-off + directed)
Replaces the live-source restriction + pair_count compensation hacks with the
correct definition: every in-range node is a betweenness source, every shortest
path credits its intermediates, and `live` becomes purely an output filter.
Fixes two issues:
- Edge roll-off: routes that pass through the inner area but start/end in the
buffer (buffer->buffer) were dropped, under-crediting boundary nodes. Now
counted. (With buffer >= d_max the routes are real, not truncation artifacts.)
- Directed betweenness was halved: s->t and t->s are distinct ordered flows, not
one undirected pair seen twice. pair_count is now `if is_directed {1.0} else {0.5}`
- the per-pair 0.5 is exactly the global /2 for undirected symmetric orderings.
Mechanics:
- Removed `source_eligible`; a buffer source now skips its Dijkstra only when it
can't contribute (no betweenness AND exact mode) - so closeness-only exact runs
keep today's performance.
- Closeness/cycles guarded with `is_sampling || node_live[src]`: exact mode
aggregates at the (live) source; sampling target-aggregates onto live nodes via
buffer sources. Closeness values are unchanged.
Behaviour: all-live betweenness, all closeness, and the NetworkX comparison are
unchanged; only buffered betweenness shifts (boundary nodes rise) and directed is
fixed. Re-baselined the dead-buffer test (B: 3.0 -> 4.0, the D1<->D2 route) and
added a directed one-way-loop regression (3.0, not 1.5). Exact and sampling now
agree on the all-routes values.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y; drop synthetic Recalibrate the schedule so it has one tuned knob. The canonical grid spacing s=175m is now a fixed reference (not fitted); ε is the single calibrated parameter, tuned on the sparsest validated network so the rest are covered by construction. Default ε: 0.06 → 0.05. All three networks pass ρ≥0.95 at 1–20km (overall min 0.96 = Cary closeness at 20km). - Add Cary, NC as a third validation network: a low-density planned suburb (mean degree ~2.5) built from TIGER/Line *edges* (natively noded; the roads layer is not), with the live-area boundary geocoded from OSM. It is the binding case for the calibration — the real-world counterpart of low-connectivity development. New: 03_validate_cary.py, fetch_tiger_cary.py, and the cary_epsilon_sweep / cary_s_sweep calibration diagnostics. - Drop the synthetic networks: the trellis/linear topologies were Spearman tie-degenerate (constant centrality → ρ undefined under any noise) and uninformative; the three real networks now span dense→sparse directly. Removed 00_generate_cache.py and 01_analyse_synthetic.py; renumbered validations to 01/02/03; reworked 04_figures/05_macros to use Cary, not synthetic. - Per-metric exact speedup baselines (closeness sources n_live, betweenness n_total) and a buffer-containment guard (assert the road-mask lies within the available data) in utilities + the loaders. - Rewrite the paper around the clean argument: localised catchments → fixed canonical grid (s) → ε tuned on Cary. Correct the Madrid road source to the official Red Viaria network (not OSM); state that all live-area boundaries come from OSM. Remove Fig 1 / Table 1 / the synthetic appendix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…prose Citation audit (web-verified) caught real bibliographic errors: - Brandes2007: wrong venue/DOI (DOI resolved to an unrelated paper) → correct to Int. J. Bifurcation & Chaos 17(7):2303-2318, doi 10.1142/S0218127407018403. - Borassi2019 (KADABRA): cited DOI resolved to an unrelated ESA paper; "ESA 2019" does not exist → correct to ACM J. Experimental Algorithmics 24(1), doi 10.1145/3284359. - Bergamini2019: author list was fabricated (real authors are Matta, Ercal, Sinha); article no. 5→2. - Cooper2018 (sDNA): DOI did not resolve and venue/year/vol/pages were wrong → correct to SoftwareX 12:100525, 2020, doi 10.1016/j.softx.2020.100525. - Eppstein2004 pages 27-34→39-45; Pellegrina2023 pages 1-40→1-55; Freeman1979 year→1978; Simons2022 add vol 50(5):1328-1344; Turner2007 add issue no. 3. - Drop the loose Strano2013/Boeing2017 "block length 80-180m" attribution (OSMnx paper is not a block-length source; Strano reports ~60-120m mean segment length) and reframe the s=175m justification accordingly. Prose: remove duplicate boundary-effects paragraph, duplicate schedule-table intro, and duplicate exact-mode explanation; fold the standalone "why conservative" subsection into the Discussion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tighten the comparability paragraph; merge the two betweenness-sharing paragraphs; defer the reach-vs-canonical over/under-sampling discussion to the grid-model section (removing a duplicate); correct the limitations note (sparse networks rely on the calibrated tolerance, not 'the bound's conservatism'). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add CITATIONS_AUDIT.md: a web-verified record for every cited work (canonical URL, verified metadata, the claim it supports, verbatim quotes where retrievable, synopsis, and an in-context check), plus the table of corrections this audit produced. Remaining fixes from the audit: - rename mislabelled key Bergamini2019 -> Matta2019 (real authors Matta, Ercal, Sinha); - correct the in-text Strano2013 street-segment range to the verified city means (~95-122 m), having already dropped the mis-attributed Boeing2017 co-cite. Eppstein2004 pages re-confirmed as 39-45 (JGAA publisher page); no change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the practical-guidance sentence that repeated the figure caption; fold the two-sentence Future Work subsection into Limitations; correct the appendix to point at cityseer.sampling (not cityseer.config) for the default parameters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The paper defined the harmonic form but did not say why it is used. Add the rationale to Preliminaries: under a distance threshold most of the network is unreachable and each catchment is a different size, where classic closeness (reciprocal of summed distances) is ill-defined/unstable; harmonic closeness sums bounded per-node contributions (unreachable nodes contribute zero), staying well-defined and comparable across differently sized catchments while keeping the same intention. Its additive form is also what makes the per-source IPW estimate well-defined under sampling. Cite Boldi & Vigna (2014) "Axioms for Centrality" (harmonic uniquely satisfies the size/density axioms; a correction to classic closeness for unreachable nodes) alongside Rochat (2009). Verified against the literature and cityseer's own docs; Boldi2014 added to CITATIONS_AUDIT.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cile figures; drop author The previous "residuals are spatially uniform, no systematic concentration" claim was wrong and contradicted our own error-structure finding (absolute error grows with reach). Multi-seed testing on GLA shows: - residual magnitude tracks the signal (largest in dense cores / high-betweenness corridors), and residuals are strongly spatially autocorrelated (neighbouring nodes share sampled sources), so a single realisation shows smooth same-sign patches; - there is no directional bias: the apparent east-west gradient in the GLA closeness map reverses sign across seeds (corr-with-x: +0.09/-0.20/-0.15/+0.13/+0.08/-0.03, mean ~0). London does have a real E-W reach/density gradient (corr(reach,x)=-0.32) that sets where residuals are *larger*, but not their sign; - mean relative bias ~1-3%, and relative error is smallest where absolute residuals are largest, so rankings are preserved. Rewrite the error-structure text and the fig7/fig11 captions accordingly. Add Cary to the spatial-residual figure (fig7, now 3 rows) and the decile-transition figure (fig11, now 6 panels) via a generic per-network loop in 06_figures_spatial.py. Remove the author block for now (co-author pending; original kept commented). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… is 0.05) The library default is correct (sampling.HOEFFDING_EPSILON=0.05, GRID_SPACING=175, runtime computes p accordingly), but a docstring example and several doc references still said 0.06. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An absolute-residual map is misleading: residuals are large in absolute terms exactly where the values are large, even though those nodes scarcely change rank, so the map looked alarming despite ranks being preserved. Replace it with the on-claim quantity: per-node rank shift |percentile(true) - percentile(sampled)| in percentile points, drawn as the hexbin median per cell (tail-robust; binning avoids overplotting small values into apparent saturation) on a fixed 0-10 white->red scale. The maps are pale almost everywhere: the median node moves ~1-2 percentile points on the metros, ~2-3.5 on Cary. Update caption + error-structure text accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 6-panel rank-shift and decile figures (3 networks) overran the page with their captions. Put both on dedicated float pages ([p]), reduce widths (0.82/0.72 textwidth), and tighten the rank-shift caption. No more overfull vboxes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ference Remove the now-unused _spatial_residual_panel (and its orphaned CMAP_DIVERGING) left over from the switch to the rank-shift figure. Add a one-line pointer under the 'Spatial error distribution' heading so the [p]-floated figures don't leave a blank page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- tests: update test_constants_match_paper to the calibrated defaults (eps=0.05, s=175) — was asserting the pre-calibration eps=0.06 and failing the suite. - qgis plugin: fix false version mismatch — metadata spelling (4.25.0beta24) was string-compared against pip's PEP 440-normalised form (4.25.0b24), so every install reported a mismatch. Normalise both sides via a shared _normalize_version (hyphen/dot/underscore-agnostic alpha/beta/rc -> a/b/rc). - networks: document the cc_ column convention (managed prefix; recomputing a metric overwrites matching columns in place) in the module notes; docs regenerated. - RELEASE_NOTES.md: add the missing v4.25.0 entry — expression-based centrality, CityNetwork, betweenness all-routes redesign (with value-change consequences), directed networks, betweenness_demand, sampling recalibration (eps=0.05 on three real networks), stats measure selection, removals with migrations, the backwards-compatibility contract, and fixes. - analysis scripts: lint fixes (import order, context-managers for pickle reads), refresh stale figure list in 06 header. Full verify_project gate green (ruff format+check, ty, pytest 179 passed). Backward-compat verified end-to-end: v4.24.1 docs examples run verbatim; all 30 functions used by the examples repo exist; old names produce the 4.24 default columns with deprecation warnings; removals raise clear errors per COMPATIBILITY.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…coverage gaps From the pre-release docs audit: - guide + set_boundary docstring described dead nodes as "excluded from centrality source computations" — contradicting the 4.25 betweenness redesign (every node sources; live filters reporting). Rewritten with the correct per-metric semantics. - guide still stated the pre-calibration epsilon default (0.06 -> 0.05, with the calibration story). - metrics/sampling.md "Accuracy" section described the abandoned two-model design; rewritten for the unified schedule, the eps=0.05 calibration on three real networks, and a high-level (CityNetwork) usage example. - Fixed all broken internal links at the docstring/page source: 23 wrong routes (/io#... -> /tools/io#..., /rustalgos#datamap -> /rustalgos/data#datamap, /rustalgos/rustalgos#networkstructure -> /rustalgos/graph#networkstructure) and ~90 hyphenated anchors that never matched the underscore slugs emitted by rehype-slug (e.g. #centrality-shortest -> #centrality_shortest). Validated programmatically: 124/124 internal links now resolve (0 broken routes/anchors). - COMPATIBILITY.md references in deprecation notes are now links to the GitHub blob (was plain text a site visitor couldn't follow). - /tools/util added to the site nav (was generated but orphaned). - Overpass QL block in io.py fenced as text, not python. - Docs regenerated; verify_project green; Astro build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Independent recomputation confirmed every macro/table value matches the committed validation data. Fixes from the audit: - main.tex: cityseer.config -> cityseer.sampling for the parameter home; scope the sampled-distances sentence correctly for Cary (20km only; 10km is exact-fallback); replace the unsupported "several hours on a single core" with a claim grounded in the measured baselines. - Delete orphaned pre-calibration artifacts that contradicted the paper (tab1 with an eps=0.06 caption; fig1 with no generator). - cary_epsilon_sweep.py: header now correctly presents the sweep as the calibration evidence for eps=0.05; cary_s_sweep.py: marked as a historical pre-calibration diagnostic (s stays fixed; eps is the calibrated knob). - utilities/validation scripts: cityseer.config -> cityseer.sampling references. Paper recompiles clean (17pp). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n, not a fix The previous exclusion of routes that both start and end outside the boundary was an intentional scoping choice (such routes are relatively uncommon but require every buffer node to source, adding substantial computational weight). 4.25 deliberately opts for theoretical strictness and counts them — they are the only difference on undirected buffered networks. Rewritten in RELEASE_NOTES and the guide accordingly; removed "fixes"/"now correct"/"roll-off" framing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fourth network addresses the two main referee objections identified in the pre-submission audit: the calibrate-on-Cary/validate-on-Cary circularity, and the small number of validation networks. The Woodlands (a master-planned dendritic suburb north of Houston; 43k nodes, mean degree 2.50, phi 0.105) plays no part in selecting any parameter; the default schedule (eps=0.05, s=175m) is applied to it exactly as shipped. Held-out results: the target (rho >= 0.95) is met at every distance for betweenness (20km: 0.958) and for closeness to 10km; 20km closeness falls marginally short (0.939). The shortfall is mechanical: at 20km The Woodlands reaches 38% of the canonical reach, against 51% for Cary, so the fixed schedule under-samples it more severely than any calibration network. An epsilon sweep confirms eps=0.04 restores the target (rho=0.954). The paper reports this as a boundary-of-validity finding, consistent with the existing guidance that networks sparser than the calibration network require a tighter tolerance. Pipeline changes: - fetch_tiger.py replaces fetch_tiger_cary.py (place/CRS/out parameterised, with download retries); epsilon_sweep.py replaces cary_epsilon_sweep.py (--network). - 04_validate_woodlands.py added; downstream scripts renumbered to 05_figures_validation / 06_generate_macros / 07_figures_spatial and extended to four networks (tables tab6, figures, spatial and decile panels). - New macros: woodlands* series and per-suburb reach ratios; the overall minimum rho remains defined over the calibration-range networks, with the held-out network reported separately. - Committed outputs: woodlands validation, bound analysis, and epsilon sweep CSVs. Paper: abstract, contributions, setup, calibration, results, limitations, and conclusion updated for four networks with the held-out finding stated plainly. Full prose pass applied at the same time: em-dash constructions and rhetorical phrasing replaced with plain academic register throughout (26 passages). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sampling page now reports the held-out validation outcome: the default eps=0.05 preserves rankings on metropolitan and typical suburban networks, while a very sparse dendritic suburb requires eps=0.04 at 20km. The guide links to this guidance. All em dashes in the two pages replaced with plain punctuation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… schedule sample=True now measures each node's reach with a KD-tree pilot (Euclidean counts deflated by 2.5, calibrated against measured Euclidean-to-network ratios on the four validation networks) and assigns each node its own inclusion probability q = min(1, k(r)/r). Sparse areas sample more heavily, dense areas less, so every catchment receives approximately the Hoeffding-required effective samples and precision is uniform across the network. Per-source 1/q weighting keeps estimates unbiased regardless of pilot quality. A per-distance work test selects exact computation wherever powered sampling cannot undercut exact cost; nodes with reach below k saturate at q = 1, which is per-node exactness. The Rust layer already supported per-node sampling_weights; the change is Python-only. The distance-only canonical schedule remains in cityseer.sampling as a reference model. Validation against the cached exact baselines (all four networks): the held-out Woodlands failure is resolved (20km betweenness 0.969 sampled, closeness routed to exact by the work test), Cary improves, and the metros are unchanged at ~0.99 with marginally less sampling. Minimum rho across all four networks and both metrics: 0.97. Paper restructured accordingly: retitled to reach-based sampling; canonical schedule presented as the zero-knowledge baseline with its held-out failure as motivating evidence; per-node method as the contribution, with a new algorithm block and an adaptive results subsection. New figures: a four-panel worked-example schematic (pilot, per-node q, fixed-rate contrast, per-node draw; PDF + SVG), a baseline vs adaptive comparison (20km bars plus per-quartile uniformity), and an annotated assumed-vs-actual reach gap in the reach figure. The comparability argument corrected throughout: equal uncertainty, not equal protocol. Docs, release notes, argument notes, and manifest updated; validate_adaptive.py added to reproduce the comparison. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migrate the cityseer-examples repository into examples/ as marimo notebooks and rebuild the documentation site around the v5 CityNetwork API: - Astro 5 -> 7 upgrade; four-section IA (Start / Guide / Examples / API) with sidebar-only navigation, Pagefind search, and legacy-URL redirects - 41 notebooks: converted to marimo, modernized to CityNetwork throughout (functional API only where no equivalent exists, each labelled), district- scale study areas, publication-standard figures, OSM/ODbL attribution at point of display - new content: interpretation, troubleshooting, and v4-to-v5 migration guide pages; results-to-maps, custom-expressions, sampled-centrality, od-betweenness, and directed-networks recipes - notebook publishing pipeline: local execute-and-export to static HTML with incremental skips, per-page downloads, and a gh-pages deploy script replacing Firebase hosting - library support: Overpass User-Agent fix, quiet mode governs logging and all progress bars, docstring link and version updates - version 5.0.0 with reframed release notes and compatibility contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Method: the sampling pilot now measures reach by polling the network (a bounded Dijkstra from m = max(400, 2.5% of nodes) sampled sources) instead of counting Euclidean neighbours, so barriers and dead ends are priced in. Per-node inclusion probabilities derive from a Clopper-Pearson lower bound on reach and the work test from the upper bound, so estimation error errs toward oversampling. Adds a 0.75 work-test margin and leaves one or two CPU cores free by default. Validation: rerun on four networks with per-metric exact/sampled modes (minimum rho 0.962 on held-out Woodlands betweenness at 20 km); adds a 50 km frontier test where sampled betweenness plateaus at rho 0.953-0.955 through 30-50 km. Paper: restructured to SCAFFOLD.md (survey framing, three-rung ablation, canonical-vs-method validation), a prose-cohesion pass, and a unified figure design system (figstyle.py). New analysis scripts: barrier example, frontier, disc-reach ratios, forced-closeness check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…coverage evidence Paper: abstract and introduction reframed on the story arc (sampling as an appealing remedy with a design problem, not current practice); formal register restored after the accessibility rounds; method schematic promoted to Figure 1 with takeaway-first captions; work-test schematic, parameter, error-direction, and ablation-ladder tables added; per-distance mode vs per-node adaptivity spelled out; length cut and technical detail moved to tables, captions, and the appendix. Frontier: redone as one build - the 50 km-buffered Woodlands validated across the full 1-50 km range (11_frontier_woodlands.py --distances), so the accuracy panel is a single continuous series; speedup literals updated to the timing session of record (6-14x). Pilot: new pilot_coverage_check.py validates the reach bounds against exact reach (10 draws, four networks); appendix paragraph and generated table document unbiased estimates, near-nominal coverage, and draw-correlated errors with 1-3% overshoots. Docs: sampling page and centrality guide embed the method and work-test schematics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n new API ruff: exempt marimo notebooks from E501/B018 (notebook idiom), format sweep across analysis scripts and notebooks, explicit zip strictness, rewrap three over-long docstrings in layers.py. tests: route every Overpass call in test_osm_graph_from_poly through the graceful-skip guard so rate-limiting (429) skips instead of failing verify_project. examples: localised_analysis case study moved to the hybrid API (CityNetwork throughout; nx_decompose remains the one tools-level step). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The union_all() type-ignore in osm_graph_from_poly was unused on CI's Linux stubs (failing ty check and blocking the v5.0.0 publish job) but required on local macOS stubs. Replace it with typing.cast, which has no runtime effect and is never itself flagged as an unused suppression, so ty passes across all four CI Python versions and both platforms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Class based API