refined-quads 0.1.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.
- refined_quads-0.1.0/.gitattributes +4 -0
- refined_quads-0.1.0/.gitignore +18 -0
- refined_quads-0.1.0/.hypothesis/.gitignore +9 -0
- refined_quads-0.1.0/.hypothesis/constants/00ac74e5f24a8f23 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/00b099b28ab69719 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/03071a56d568c4ff +4 -0
- refined_quads-0.1.0/.hypothesis/constants/0ca6499cddf7bb4e +4 -0
- refined_quads-0.1.0/.hypothesis/constants/0d3d930a8cf43bfe +4 -0
- refined_quads-0.1.0/.hypothesis/constants/17ee048a05047466 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/1f66879254faba2d +4 -0
- refined_quads-0.1.0/.hypothesis/constants/25497450f93ddf0a +4 -0
- refined_quads-0.1.0/.hypothesis/constants/28260f4e3ba16f80 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/39328d456621aedd +4 -0
- refined_quads-0.1.0/.hypothesis/constants/3e605759b9d714fe +4 -0
- refined_quads-0.1.0/.hypothesis/constants/3f2b180b637013e0 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/46f7bb725c91ddeb +4 -0
- refined_quads-0.1.0/.hypothesis/constants/60081ff99c20a9d1 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/6de9487054c298a0 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/8814f21e6484e0fc +4 -0
- refined_quads-0.1.0/.hypothesis/constants/88daa58db1ff125a +4 -0
- refined_quads-0.1.0/.hypothesis/constants/9182c701def2d4d5 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/989da628c6044bfc +4 -0
- refined_quads-0.1.0/.hypothesis/constants/a5631cf79c8a54c4 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/b197677f5f08a04f +4 -0
- refined_quads-0.1.0/.hypothesis/constants/b747fbea3787abd2 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/b7542176cfbb5401 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/bdb6ab1d246d2c55 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/c70157c5272366f2 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/cbaeb266738a869f +4 -0
- refined_quads-0.1.0/.hypothesis/constants/dd7ed633ab48ff73 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/e5330bdc0ad9bcb0 +4 -0
- refined_quads-0.1.0/.hypothesis/constants/fc51d84a26877bba +4 -0
- refined_quads-0.1.0/.pre-commit-config.yaml +40 -0
- refined_quads-0.1.0/.python-version +1 -0
- refined_quads-0.1.0/.yamllint.yaml +9 -0
- refined_quads-0.1.0/CHANGELOG.md +147 -0
- refined_quads-0.1.0/CITATION.cff +22 -0
- refined_quads-0.1.0/CLAUDE.md +53 -0
- refined_quads-0.1.0/CONTEXT.md +80 -0
- refined_quads-0.1.0/LICENSE +674 -0
- refined_quads-0.1.0/PKG-INFO +139 -0
- refined_quads-0.1.0/README.md +110 -0
- refined_quads-0.1.0/docs/.gitignore +8 -0
- refined_quads-0.1.0/docs/_quarto.yml +112 -0
- refined_quads-0.1.0/docs/adr/0001-refine-from-existing-mesh.qmd +31 -0
- refined_quads-0.1.0/docs/adr/0002-output-contract-same-cells.qmd +30 -0
- refined_quads-0.1.0/docs/adr/index.qmd +9 -0
- refined_quads-0.1.0/docs/agents/domain.md +43 -0
- refined_quads-0.1.0/docs/agents/issue-tracker.md +21 -0
- refined_quads-0.1.0/docs/agents/triage-labels.md +19 -0
- refined_quads-0.1.0/docs/explanation/orthogonality.qmd +52 -0
- refined_quads-0.1.0/docs/explanation/refine.qmd +57 -0
- refined_quads-0.1.0/docs/explanation/refined-quad-mesh.qmd +72 -0
- refined_quads-0.1.0/docs/how-to/d-hydro.qmd +75 -0
- refined_quads-0.1.0/docs/how-to/gis-layers.qmd +103 -0
- refined_quads-0.1.0/docs/how-to/refine.qmd +116 -0
- refined_quads-0.1.0/docs/how-to/toml-cli.qmd +119 -0
- refined_quads-0.1.0/docs/how-to/water-areas.qmd +88 -0
- refined_quads-0.1.0/docs/index.qmd +74 -0
- refined_quads-0.1.0/docs/snippets/plot_mesh.py +71 -0
- refined_quads-0.1.0/docs/tutorial/first-mesh.qmd +96 -0
- refined_quads-0.1.0/pyproject.toml +127 -0
- refined_quads-0.1.0/src/refined_quads/__init__.py +52 -0
- refined_quads-0.1.0/src/refined_quads/__main__.py +7 -0
- refined_quads-0.1.0/src/refined_quads/_check/__init__.py +1 -0
- refined_quads-0.1.0/src/refined_quads/_check/check.py +196 -0
- refined_quads-0.1.0/src/refined_quads/_common/__init__.py +1 -0
- refined_quads-0.1.0/src/refined_quads/_common/grid.py +138 -0
- refined_quads-0.1.0/src/refined_quads/_common/progress.py +51 -0
- refined_quads-0.1.0/src/refined_quads/_common/recognise.py +343 -0
- refined_quads-0.1.0/src/refined_quads/_common/topology.py +142 -0
- refined_quads-0.1.0/src/refined_quads/_generate/__init__.py +1 -0
- refined_quads-0.1.0/src/refined_quads/_generate/areas.py +221 -0
- refined_quads-0.1.0/src/refined_quads/_generate/balance.py +223 -0
- refined_quads-0.1.0/src/refined_quads/_generate/clip.py +122 -0
- refined_quads-0.1.0/src/refined_quads/_generate/conformity.py +274 -0
- refined_quads-0.1.0/src/refined_quads/_generate/engine.py +353 -0
- refined_quads-0.1.0/src/refined_quads/_generate/faces.py +197 -0
- refined_quads-0.1.0/src/refined_quads/_generate/generate.py +359 -0
- refined_quads-0.1.0/src/refined_quads/_generate/orthogonality.py +623 -0
- refined_quads-0.1.0/src/refined_quads/_generate/quadtree.py +154 -0
- refined_quads-0.1.0/src/refined_quads/_generate/select.py +72 -0
- refined_quads-0.1.0/src/refined_quads/_io/__init__.py +1 -0
- refined_quads-0.1.0/src/refined_quads/_io/gis.py +414 -0
- refined_quads-0.1.0/src/refined_quads/_io/ugrid.py +312 -0
- refined_quads-0.1.0/src/refined_quads/_refine/__init__.py +1 -0
- refined_quads-0.1.0/src/refined_quads/_refine/refine.py +308 -0
- refined_quads-0.1.0/src/refined_quads/cli.py +245 -0
- refined_quads-0.1.0/src/refined_quads/errors.py +70 -0
- refined_quads-0.1.0/src/refined_quads/mesh.py +234 -0
- refined_quads-0.1.0/src/refined_quads/py.typed +0 -0
- refined_quads-0.1.0/src/refined_quads/spec.py +602 -0
- refined_quads-0.1.0/tests/check/test_check.py +173 -0
- refined_quads-0.1.0/tests/generate/test_benchmarks.py +48 -0
- refined_quads-0.1.0/tests/generate/test_generate.py +131 -0
- refined_quads-0.1.0/tests/generate/test_golden_masters.py +96 -0
- refined_quads-0.1.0/tests/generate/test_progress_and_origin.py +121 -0
- refined_quads-0.1.0/tests/generate/test_repair.py +75 -0
- refined_quads-0.1.0/tests/generate/test_repair_failure.py +100 -0
- refined_quads-0.1.0/tests/generate/test_water_areas.py +176 -0
- refined_quads-0.1.0/tests/golden/__init__.py +0 -0
- refined_quads-0.1.0/tests/golden/benchmarks.py +111 -0
- refined_quads-0.1.0/tests/golden/compare.py +87 -0
- refined_quads-0.1.0/tests/golden/masters/cell_size_12_5.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/cell_size_24_four_levels.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/cell_size_25.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/cell_size_32.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/domain_with_hole.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/head_to_toe_boxes.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/head_to_toe_circles.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/l_shaped_domain.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/many_small_water_areas.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/refinement_at_domain_edge.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/transition_rows_3_2_1_1.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/water_removal_centre.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/water_removal_inside.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters/water_removal_touches.npz +0 -0
- refined_quads-0.1.0/tests/golden/masters.py +90 -0
- refined_quads-0.1.0/tests/golden/realdata_manifest.example.toml +18 -0
- refined_quads-0.1.0/tests/golden/record.py +463 -0
- refined_quads-0.1.0/tests/golden/repair/adjacent_wrong.npz +0 -0
- refined_quads-0.1.0/tests/golden/repair/fan_from_corner.npz +0 -0
- refined_quads-0.1.0/tests/golden/repair/fan_from_south.npz +0 -0
- refined_quads-0.1.0/tests/golden/repair/fan_from_south_10_1.npz +0 -0
- refined_quads-0.1.0/tests/golden/repair/wrong_diagonal.npz +0 -0
- refined_quads-0.1.0/tests/golden/repair_cases.py +93 -0
- refined_quads-0.1.0/tests/golden/runtime_baseline.json +96 -0
- refined_quads-0.1.0/tests/golden/scenarios.py +240 -0
- refined_quads-0.1.0/tests/golden/test_benchmark_report.py +78 -0
- refined_quads-0.1.0/tests/golden/test_compare.py +71 -0
- refined_quads-0.1.0/tests/golden/test_realdata.py +38 -0
- refined_quads-0.1.0/tests/golden/test_realdata_selection.py +38 -0
- refined_quads-0.1.0/tests/golden/ugrid/domain_with_hole_legacy.nc +0 -0
- refined_quads-0.1.0/tests/handmade.py +58 -0
- refined_quads-0.1.0/tests/io/test_gis.py +208 -0
- refined_quads-0.1.0/tests/io/test_ugrid.py +227 -0
- refined_quads-0.1.0/tests/refine/test_refine.py +241 -0
- refined_quads-0.1.0/tests/refine/test_refine_foreign.py +155 -0
- refined_quads-0.1.0/tests/test_cli.py +217 -0
- refined_quads-0.1.0/tests/test_cli_refine.py +138 -0
- refined_quads-0.1.0/tests/test_mesh.py +72 -0
- refined_quads-0.1.0/tests/test_package.py +27 -0
- refined_quads-0.1.0/uv.lock +2745 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.venv/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Tool caches
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
.benchmarks/
|
|
14
|
+
.coverage
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# Local issue tracker and scratch work (see docs/agents/issue-tracker.md)
|
|
18
|
+
.scratch/
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
|
|
2
|
+
# .hypothesis by default, because we generally recommend that .hypothesis not be checked
|
|
3
|
+
# into version control.
|
|
4
|
+
#
|
|
5
|
+
# If you *would* like to check .hypothesis into version control, you should delete this
|
|
6
|
+
# file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
|
|
7
|
+
# if it does, that's a bug - please report it!)
|
|
8
|
+
|
|
9
|
+
*
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_io/gis.py
|
|
2
|
+
# hypothesis_version: 6.170.15
|
|
3
|
+
|
|
4
|
+
[-1.0, 'EPSG_code', 'GPKG', 'MultiPolygon', 'Point', 'Polygon', 'Unknown projected', 'WKT1_GDAL', 'columns', 'crs', 'domain', 'epsg', 'fields', 'geometries', 'geometry', 'grid_mapping_name', 'ignore', 'index', 'inverse_flattening', 'levels', 'name', 'orth_e', 'proj4_params', 'projection_name', 'refinement area', 'semi_major_axis', 'semi_minor_axis', 'unknown', 'value', 'water area', 'wkt']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/spec.py
|
|
2
|
+
# hypothesis_version: 6.170.15
|
|
3
|
+
|
|
4
|
+
[', ', '.', 'WaterRemovalRule', 'cell_size', 'cell_size: missing', 'centre', 'crs', 'domain', 'domain: missing', 'layer', 'level', 'origin', 'output', 'path', 'refinement', 'refinement_areas', 'transition_rows', 'utf-8', 'water_areas', 'water_removal']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/__init__.py
|
|
2
|
+
# hypothesis_version: 6.170.15
|
|
3
|
+
|
|
4
|
+
['0.0.0', 'Areas', 'Cancelled', 'InputError', 'Lattice', 'LayerSource', 'Mesh', 'MeshRecognitionError', 'MeshReport', 'MeshSpec', 'RefineResult', 'RefineSpec', 'RefinementSource', 'ReplacementMap', '__version__', 'check', 'generate', 'read_domain', 'read_ugrid', 'read_water_areas', 'refine', 'refined-quads', 'write_ugrid']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/cli.py
|
|
2
|
+
# hypothesis_version: 6.170.15
|
|
3
|
+
|
|
4
|
+
[254, '+', '--cell-size', '--crs', '--origin', '--output', '--transition-rows', '--verbose', '--version', '--water-removal', '-o', '-v', ';', 'COMMAND', 'N', 'X', 'Y', 'cell_size', 'command', 'crs', 'generate', 'info', 'mesh', 'none', 'origin', 'output', 'problem', 'recognition_problems', 'refine', 'refined-quads', 'spec', 'store_true', 'transition_rows', 'version', 'water removal rule', 'water_removal']
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_io/ugrid.py
|
|
2
|
+
# hypothesis_version: 6.170.15
|
|
3
|
+
|
|
4
|
+
[-999.0, 0.5, 299.1528128, 6356078.962818189, 6377397.155, -999, 28992, '%Y-%m-%dT%H:%M:%S', ', ', '0', 'Amersfoort / RD New', 'Conventions', 'EPSG:0', 'EPSG:28992', 'EPSG_code', 'HKV / Svasek', 'Mesh2d', 'Mesh2d_edge_nodes', 'Mesh2d_edge_x', 'Mesh2d_edge_y', 'Mesh2d_face_nodes', 'Mesh2d_face_x', 'Mesh2d_face_x_bnd', 'Mesh2d_face_y', 'Mesh2d_face_y_bnd', 'Mesh2d_face_z', 'Mesh2d_nEdges', 'Mesh2d_nFaces', 'Mesh2d_nNodes', 'Mesh2d_node_x', 'Mesh2d_node_y', 'Mesh2d_node_z', 'NETCDF4', 'Two', 'Unknown projected', 'altitude', 'bounds', 'cf_role', 'crs_wkt', 'edge_coordinates', 'edge_dimension', 'epsg', 'f8', 'face_coordinates', 'face_dimension', 'grid_mapping_name', 'history', 'i4', 'institution', 'inverse_flattening', 'long_name', 'm', 'mesh_topology', 'name', 'node_coordinates', 'node_dimension', 'proj4_params', 'projection_name', 'r', 'references', 'semi_major_axis', 'semi_minor_axis', 'source', 'standard_name', 'start_index', 'topology_dimension', 'units', 'unknown', 'value', 'w', 'wkt', 'x', 'x-coordinate', 'y', 'y-coordinate']
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Install once with `uv run pre-commit install`; run on everything with
|
|
3
|
+
# `uv run pre-commit run --all-files`. CI runs the same hooks.
|
|
4
|
+
repos:
|
|
5
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
6
|
+
rev: v6.0.0
|
|
7
|
+
hooks:
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
- id: check-ast
|
|
10
|
+
- id: check-case-conflict
|
|
11
|
+
- id: check-merge-conflict
|
|
12
|
+
- id: check-toml
|
|
13
|
+
- id: debug-statements
|
|
14
|
+
- id: end-of-file-fixer
|
|
15
|
+
- id: trailing-whitespace
|
|
16
|
+
args: [--markdown-linebreak-ext=md]
|
|
17
|
+
|
|
18
|
+
- repo: https://github.com/adrienverge/yamllint
|
|
19
|
+
rev: v1.38.0
|
|
20
|
+
hooks:
|
|
21
|
+
- id: yamllint
|
|
22
|
+
args: [--config-file=.yamllint.yaml]
|
|
23
|
+
|
|
24
|
+
# No pydoclint (in the HKV template): it compares the types in docstrings with the
|
|
25
|
+
# annotations, and the docstrings here describe types in words ("list of (float, float)").
|
|
26
|
+
# ruff checks the docstrings (numpy convention), mypy the annotations.
|
|
27
|
+
|
|
28
|
+
# ruff from the project environment, so the hooks and `uv run ruff` use one version.
|
|
29
|
+
- repo: local
|
|
30
|
+
hooks:
|
|
31
|
+
- id: ruff-check
|
|
32
|
+
name: ruff check
|
|
33
|
+
entry: uv run --frozen ruff check --fix --exit-non-zero-on-fix
|
|
34
|
+
language: system
|
|
35
|
+
types_or: [python, pyi]
|
|
36
|
+
- id: ruff-format
|
|
37
|
+
name: ruff format
|
|
38
|
+
entry: uv run --frozen ruff format
|
|
39
|
+
language: system
|
|
40
|
+
types_or: [python, pyi]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/). Any intended change to the cells a given
|
|
6
|
+
input produces is at least a minor version (see `docs/adr/0002-output-contract-same-cells.qmd`).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-11-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Project skeleton: uv project, ruff, mypy, pytest, CI workflow and documentation site.
|
|
15
|
+
- `Mesh` and `Lattice`: an immutable mesh (nodes, faces padded with -1) on its lattice.
|
|
16
|
+
- `check(mesh) -> MeshReport`: hanging nodes, 2:1 balance, FM orthogonality per internal
|
|
17
|
+
edge (the D-Flow FM measure), orientation, cells per refinement level and nodes off the
|
|
18
|
+
lattice.
|
|
19
|
+
- `InputError` for invalid input.
|
|
20
|
+
- Golden masters of the generator at qgis_dhydamo `9a324bc` for fourteen synthetic
|
|
21
|
+
scenarios, with the recording script and a runtime baseline (`tests/golden`); with
|
|
22
|
+
water or holes they are the old generator without water plus the MeshKernel clip of
|
|
23
|
+
the model workflow.
|
|
24
|
+
- `generate(domain, *, cell_size, refinement_areas, transition_rows) -> Mesh`, with the
|
|
25
|
+
engine of the generator at qgis_dhydamo `9a324bc`; it gives the same cells for the
|
|
26
|
+
same input (golden masters).
|
|
27
|
+
- `OrthogonalityRepairError` when the orthogonality repair does not close.
|
|
28
|
+
- Water areas in `generate` (`water_areas`, `water_removal`) with three water removal
|
|
29
|
+
rules: `"centre"` (default), `"inside"` and `"touches"`. The finished mesh is clipped
|
|
30
|
+
cell by cell with MeshKernel, exactly as the model workflow (hydrolib's
|
|
31
|
+
`mesh2d_clip`) does, so the remaining small cells follow the water's edge; MeshKernel
|
|
32
|
+
is a new dependency.
|
|
33
|
+
- `MeshReport.cell_levels`: the refinement level of the lattice square of every cell.
|
|
34
|
+
- `write_ugrid(mesh, path)` and `read_ugrid(path)`: UGRID-1.0 netCDF with the structure
|
|
35
|
+
of the old writer; for EPSG:28992 exactly the CRS attributes of the D-Flow FM plugin.
|
|
36
|
+
`read_ugrid` reads only the 2D mesh of a net file (a 1D network is ignored) and
|
|
37
|
+
derives the lattice from the square cells.
|
|
38
|
+
- `Mesh.crs`: the projected CRS of a mesh, or `None` when unknown; a geographic CRS
|
|
39
|
+
raises `InputError`.
|
|
40
|
+
- `read_domain`, `read_refinement_areas` (level from a column or one fixed level) and
|
|
41
|
+
`read_water_areas`: read a GeoPackage or shapefile layer with pyogrio, or take a
|
|
42
|
+
GeoDataFrame (geopandas is not a dependency), into `Areas` (polygons with a CRS).
|
|
43
|
+
- `generate` takes `Areas` and GeoDataFrames and has a `crs` parameter for input
|
|
44
|
+
without CRS. The mesh gets the CRS of the domain; other input is transformed to it.
|
|
45
|
+
- `write_ugrid` derives the CRS attributes of CRSs other than EPSG:28992 with pyproj.
|
|
46
|
+
- `MeshSpec`: the input of a run, one-to-one with a TOML file (`MeshSpec.from_toml`,
|
|
47
|
+
`replace`, `generate`); an invalid TOML file gives an `InputError` naming the key and
|
|
48
|
+
the reason.
|
|
49
|
+
- Command line: `refined-quads generate run.toml -o mesh_net.nc` (options override the
|
|
50
|
+
TOML values) and `refined-quads info mesh_net.nc` (lattice, cells per level and the
|
|
51
|
+
checks). Exit codes: 0 success, 1 the run failed, 2 invalid input or arguments.
|
|
52
|
+
- `generate(..., origin=(x, y))` (also `origin` in the TOML and `--origin X Y`): a
|
|
53
|
+
point of the lattice, so a mesh lines up with an existing mesh or raster; by
|
|
54
|
+
default the lower-left corner of the domain rounded down to a multiple of the cell
|
|
55
|
+
size, as before.
|
|
56
|
+
- `generate(..., progress=callback)`: `callback(fraction, message)` with a fraction
|
|
57
|
+
rising from 0 to 1 and English messages; returning `True` cancels the run with
|
|
58
|
+
`Cancelled` (the old generator raised a `RuntimeError`). `refined-quads generate -v`
|
|
59
|
+
prints the progress.
|
|
60
|
+
- `OrthogonalityRepairError.mesh`: the raw mesh before the repair, next to
|
|
61
|
+
`bad_points`. When the repair does not close, `refined-quads generate` writes
|
|
62
|
+
`<output>_raw.nc` and `<output>_problems.gpkg` (layer `orthogonality_problems`,
|
|
63
|
+
field `orth_e`, -1 for a degenerate edge) in the CRS of the mesh and exits with 1; a
|
|
64
|
+
failure to write them is reported without hiding the repair error.
|
|
65
|
+
- `refine(mesh, refinement_areas, ...) -> RefineResult`: refine an existing mesh
|
|
66
|
+
without its original input (ADR-0001). The lattice squares are recognised from the
|
|
67
|
+
cells, the new areas applied, the 2:1 balance restored where needed; squares that do
|
|
68
|
+
not change keep their cells and nodes exactly, the mesh never grows beyond its
|
|
69
|
+
footprint, and a domain or water areas only remove new cells. `RefineResult` has the
|
|
70
|
+
new mesh and a `ReplacementMap` (per new cell the old cell it lies in, which cells are
|
|
71
|
+
unchanged, which old cells went). `generate` is the same route from the base grid.
|
|
72
|
+
- `MeshRecognitionError` (an `InputError` with locations) when a mesh cannot be
|
|
73
|
+
refined: nodes off its lattice, cells that fill no lattice square, or a broken 2:1
|
|
74
|
+
balance. Duplicate nodes are accepted, and so are lattice squares that their cells
|
|
75
|
+
fill only partly (clipped along water); new cells in such a square stay only where
|
|
76
|
+
they lie completely within its old cells.
|
|
77
|
+
- Refine works on meshes from other programs: any lattice origin and base cell (derived
|
|
78
|
+
from the cells), and any transition cells (a pentagon, another triangulation) as long
|
|
79
|
+
as they fill a lattice square; squares that do not change keep their cells. Refine
|
|
80
|
+
judges the orthogonality only of edges next to new cells, so problems the mesh
|
|
81
|
+
already had do not stop it. Meshes of rectangles or rotated cells raise `InputError`.
|
|
82
|
+
- `refined-quads refine mesh_net.nc extra.toml -o mesh_v2_net.nc` and `RefineSpec` (the
|
|
83
|
+
TOML of a refine: new refinement areas, optional domain and water areas): same
|
|
84
|
+
options and exit codes as `generate`; a mesh that cannot be recognised exits with 2
|
|
85
|
+
and writes `<output>_recognition.gpkg` with the problem locations.
|
|
86
|
+
- `MeshReport.duplicate_nodes` (information only: cells that touch in a point lose
|
|
87
|
+
nothing) and `MeshReport.split_edges` (an edge on top of another edge with other
|
|
88
|
+
nodes: an internal wall; makes the mesh invalid).
|
|
89
|
+
- Benchmarks (`pytest -m benchmark`, pytest-benchmark) on three golden-master
|
|
90
|
+
scenarios, and `tests/golden/benchmarks.py` to compare results with a baseline and
|
|
91
|
+
report changes above 5%. CI runs slow tests and benchmarks on main and compares with
|
|
92
|
+
the previous run on main; results are kept as artifact.
|
|
93
|
+
- Documentation: a tutorial (a first mesh), how-to guides (GIS layers, TOML and the
|
|
94
|
+
command line, water areas, refining an existing mesh, D-HYDRO), explanations (refined
|
|
95
|
+
quad meshes, FM orthogonality, why refine starts from the mesh) and the API reference.
|
|
96
|
+
The site is built with Quarto, as in the HKV package template; the code on the pages
|
|
97
|
+
runs during the build and draws the mesh figures, and quartodoc writes the API
|
|
98
|
+
reference. CI publishes it to GitHub Pages from main once the repository is public.
|
|
99
|
+
- Licence: GNU GPL v3 or later (`LICENSE`), with `CITATION.cff`.
|
|
100
|
+
- pre-commit hooks (file hygiene, yamllint, pydoclint, ruff), also run in CI.
|
|
101
|
+
- Publishing to PyPI when a GitHub release is published (trusted publishing).
|
|
102
|
+
|
|
103
|
+
### Changed compared with the generator at qgis_dhydamo `9a324bc`
|
|
104
|
+
|
|
105
|
+
- Intended output change: water areas are clipped cell by cell with MeshKernel after
|
|
106
|
+
generation, instead of removing whole lattice squares before the cells are formed
|
|
107
|
+
(the old `clip_water` option). Holes in the domain are clipped the same way; the old
|
|
108
|
+
generator kept every lattice square that overlaps the domain, so cells reached into
|
|
109
|
+
a hole. MeshKernel derives cells from edges again: a single deleted cell whose edges
|
|
110
|
+
all stay comes back, which is accepted.
|
|
111
|
+
- Intended output change: no minimum cell size. A 24 m base cell asked for level 5 now
|
|
112
|
+
gives 0.75 m cells; the old generator capped the levels so that the smallest cell
|
|
113
|
+
was at least 1 m (4 levels, 1.5 m).
|
|
114
|
+
- Every face is counterclockwise; the old generator wrote about 5% of its transition
|
|
115
|
+
triangles clockwise. The cells are the same.
|
|
116
|
+
- Transition rows are given per step from coarse to fine and must be at least 1; 0
|
|
117
|
+
raises `InputError` (the old generator took them from fine to coarse and counted 0
|
|
118
|
+
as 1). Callers get the same mesh by reversing their list and replacing 0 by 1.
|
|
119
|
+
- The highest refinement level follows from the refinement areas; there is no
|
|
120
|
+
`max_refinement`. A level that is not an integer of at least 1 raises `InputError`
|
|
121
|
+
instead of being clamped.
|
|
122
|
+
- Explicit refine zones without transition rows and support for shapely < 2 are gone.
|
|
123
|
+
- The orthogonality repair works on integer lattice indices: flips, convexity and the
|
|
124
|
+
redecomposition of lattice squares are exact, and the centre nodes it adds lie exactly
|
|
125
|
+
on the lattice (the old generator computed them as float midpoints). Only the FM
|
|
126
|
+
measure uses coordinates, as D-Flow FM does.
|
|
127
|
+
- Water areas that are given are always applied; there is no `clip_water` switch that
|
|
128
|
+
silently ignored them. An unknown water removal rule raises `InputError` instead of
|
|
129
|
+
falling back to the centre rule.
|
|
130
|
+
- `write_ugrid` writes the CRS of the mesh instead of always EPSG:28992: a mesh without
|
|
131
|
+
CRS gets no CRS variable. The `source` attribute names refined-quads and its version.
|
|
132
|
+
- No CRS is ever assumed: input without CRS (a layer, a GeoDataFrame or shapely
|
|
133
|
+
geometries) raises `InputError` unless `crs=` is given, and a geographic domain CRS
|
|
134
|
+
raises `InputError` (the old generator read a layer without CRS as RD New and
|
|
135
|
+
transformed everything else to RD New). A layer whose CRS contradicts `crs=` raises
|
|
136
|
+
`InputError`, as do a missing or invalid level (naming the feature), a feature
|
|
137
|
+
without geometry or that is not a polygon, and a file with several layers when no
|
|
138
|
+
layer is named.
|
|
139
|
+
- Intended output change for invalid polygons only: a domain, refinement area or water
|
|
140
|
+
area that is not valid is made valid with shapely's `make_valid` (structure method,
|
|
141
|
+
only polygonal parts) instead of `buffer(0)`, and a warning is logged. A
|
|
142
|
+
self-intersecting polygon such as a bowtie keeps all its loops, where `buffer(0)` kept
|
|
143
|
+
one. Valid input gives the same cells.
|
|
144
|
+
- The quadtree is built one generation at a time with the geometry tests in bulk
|
|
145
|
+
(shapely's vectorised predicates on prepared geometries, one STRtree query for all
|
|
146
|
+
start cells), and the selection of lattice squares in the domain clips in bulk. The
|
|
147
|
+
cells, and their order, are unchanged; on the benchmarks generate is 3-14% faster.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
cff-version: 1.2.0
|
|
3
|
+
message: "If you use this software, please cite it using these metadata."
|
|
4
|
+
title: refined-quads
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
date-released: 2026-11-12
|
|
7
|
+
abstract: >-
|
|
8
|
+
Generate and refine refined quad meshes for D-Flow FM: axis-aligned squares on a
|
|
9
|
+
fixed lattice, locally refined by repeated halving, with transition cells only where
|
|
10
|
+
refinement levels meet.
|
|
11
|
+
authors:
|
|
12
|
+
- name: "HKV lijn in water"
|
|
13
|
+
- name: "Svasek Hydraulics"
|
|
14
|
+
repository-code: "https://github.com/HKV-products-services/refined-quads"
|
|
15
|
+
url: "https://hkv-products-services.github.io/refined-quads"
|
|
16
|
+
license: GPL-3.0-or-later
|
|
17
|
+
keywords:
|
|
18
|
+
- mesh
|
|
19
|
+
- quadtree
|
|
20
|
+
- UGRID
|
|
21
|
+
- D-Flow FM
|
|
22
|
+
- hydrodynamics
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# CLAUDE.md — refined-quads
|
|
2
|
+
|
|
3
|
+
Python package that generates and refines refined quad meshes for D-Flow FM. It is the
|
|
4
|
+
single source of the generator formerly vendored as `meshgen` in qgis_dhydamo
|
|
5
|
+
(`tools/runtime/mesh/meshgen`) and DHYDRO_Varianten (`tools/standalone/measures_cli/rooster/meshgen`).
|
|
6
|
+
Plan: `.scratch/issues/refined-quads/PRD.md` and the issues next to it.
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
- Everything in English: code, docstrings (numpy style), error and log messages, docs.
|
|
11
|
+
Conversation with the user may be in Dutch.
|
|
12
|
+
- Output contract (ADR-0002): the same input gives the same cells as the generator at
|
|
13
|
+
qgis_dhydamo `9a324bc`, compared on integer lattice indices; cell and node order are
|
|
14
|
+
free. Golden masters guard this; an intended output change is at least a minor
|
|
15
|
+
version with a CHANGELOG entry.
|
|
16
|
+
- Replacing own code by shapely/numpy or vectorising: only if the golden masters still
|
|
17
|
+
give the same cells and the benchmark is not more than 5% slower.
|
|
18
|
+
- Invalid input raises an error; never silently adjust (levels, transition rows, CRS).
|
|
19
|
+
Exception: invalid polygons (domain, refinement areas, water areas) are made valid
|
|
20
|
+
with `make_valid` and a logged warning, not an error.
|
|
21
|
+
- Public API only via the top-level package. Internal code lives in subpackages whose
|
|
22
|
+
name starts with an underscore, grouped by task: `_generate/`, `_refine/`, `_check/`,
|
|
23
|
+
`_io/`, `_common/` (the modules inside have plain names). The test folders mirror them
|
|
24
|
+
(`tests/generate/`, ...); tests of the public layer (`cli`, `mesh`) stay in `tests/`.
|
|
25
|
+
- Use the terms from `CONTEXT.md`.
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
| Task | Command |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| Install | `uv sync` |
|
|
32
|
+
| Fast tests | `uv run pytest` (slow/benchmark/realdata via `-m <marker>`) |
|
|
33
|
+
| Benchmarks | `uv run pytest -m benchmark --benchmark-json=after.json`, then `uv run python tests/golden/benchmarks.py after.json --baseline before.json` (5% rule: measure before and after back to back on one machine) |
|
|
34
|
+
| Lint / format | `uv run pre-commit run --all-files` (ruff check and format, yamllint, file hygiene) |
|
|
35
|
+
| Types | `uv run mypy` |
|
|
36
|
+
| Docs | in `docs/`: `uv run quartodoc build`, then `uv run quarto render` (or `quarto preview`). Quarto is a separate install (`C:\Program Files\Quarto\bin`); CI fails on any `WARN` in the render output |
|
|
37
|
+
|
|
38
|
+
uv manages Python itself (`.python-version` = 3.12); the chocolatey Python shims on the
|
|
39
|
+
development PC are broken, so do not rely on a system Python.
|
|
40
|
+
|
|
41
|
+
## Agent skills
|
|
42
|
+
|
|
43
|
+
### Issue tracker
|
|
44
|
+
|
|
45
|
+
Local markdown in `.scratch/issues/<feature>/` (outside Git). See `docs/agents/issue-tracker.md`.
|
|
46
|
+
|
|
47
|
+
### Triage labels
|
|
48
|
+
|
|
49
|
+
Default labels (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`) as a `Status:` line in the issue file. See `docs/agents/triage-labels.md`.
|
|
50
|
+
|
|
51
|
+
### Domain docs
|
|
52
|
+
|
|
53
|
+
Single-context: `CONTEXT.md` at the root, ADRs in `docs/adr/`. See `docs/agents/domain.md`.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# refined-quads
|
|
2
|
+
|
|
3
|
+
Generates 2D computational meshes of axis-aligned square cells that are locally
|
|
4
|
+
refined by halving, for use as the 2D grid of a D-Flow FM model.
|
|
5
|
+
|
|
6
|
+
## Language
|
|
7
|
+
|
|
8
|
+
**Refined quad mesh**:
|
|
9
|
+
A 2D mesh of axis-aligned squares on a fixed lattice, locally refined by
|
|
10
|
+
repeated halving, with triangles or irregular quads only where cell sizes change.
|
|
11
|
+
_Avoid_: quadtree grid, meshgen grid, Quad Grid
|
|
12
|
+
|
|
13
|
+
**Base cell**:
|
|
14
|
+
The coarsest square of a refined quad mesh; its size is the cell size the user
|
|
15
|
+
chooses, any positive length (not necessarily a power of 2).
|
|
16
|
+
_Avoid_: basiscel, coarse cell
|
|
17
|
+
|
|
18
|
+
**Refinement level**:
|
|
19
|
+
The number of halvings of the base cell; level 0 is the base cell, level N has
|
|
20
|
+
side length cell size / 2^N.
|
|
21
|
+
_Avoid_: halving level, refinement (as a number)
|
|
22
|
+
|
|
23
|
+
**Lattice**:
|
|
24
|
+
The fixed grid of points, given by an origin and the base cell size, on which
|
|
25
|
+
every square of a refined quad mesh has its corners.
|
|
26
|
+
_Avoid_: raster, grid origin
|
|
27
|
+
|
|
28
|
+
**Lattice square**:
|
|
29
|
+
A square of the quadtree: a base cell or one of the squares its halving gives,
|
|
30
|
+
aligned to the lattice at its refinement level. It becomes one square cell, or is
|
|
31
|
+
filled exactly by transition cells; after clipping along water it can be filled only
|
|
32
|
+
partly.
|
|
33
|
+
_Avoid_: parent square, quadtree cell, leaf
|
|
34
|
+
|
|
35
|
+
**Transition rows**:
|
|
36
|
+
For the step from refinement level k+1 down to level k, the number of rows of
|
|
37
|
+
level-(k+1) cells kept around the finer area before the mesh coarsens; at least 1.
|
|
38
|
+
Given per step from coarse to fine.
|
|
39
|
+
_Avoid_: buffer rows, bufferrijen, buffer
|
|
40
|
+
|
|
41
|
+
**Transition cell**:
|
|
42
|
+
A triangle or irregular quad that, together with the other transition cells in
|
|
43
|
+
the same lattice square, fills that square exactly where refinement levels meet.
|
|
44
|
+
_Avoid_: sliver, overgangsdriehoek, transition triangle
|
|
45
|
+
|
|
46
|
+
**Footprint**:
|
|
47
|
+
The area covered by the cells of an existing mesh; holes are not part of it.
|
|
48
|
+
Refining never adds cells outside it.
|
|
49
|
+
_Avoid_: extent, mesh outline
|
|
50
|
+
|
|
51
|
+
**Replacement map**:
|
|
52
|
+
The record of a refine: which existing cells were removed and, for every new
|
|
53
|
+
cell, the existing cell it lies in. Lets callers carry cell data over.
|
|
54
|
+
_Avoid_: diff, mapping, splice
|
|
55
|
+
|
|
56
|
+
## Input
|
|
57
|
+
|
|
58
|
+
**Domain**:
|
|
59
|
+
The (multi)polygon that encloses the mesh and forms its edges (the enclosure in
|
|
60
|
+
D-HYDRO terms); usually a polygon layer of a GeoPackage, whose features are merged. A
|
|
61
|
+
lattice square that overlaps it is kept whole, so the outer edge of the mesh follows
|
|
62
|
+
the lattice along the domain rather than the polygon exactly. Holes in the domain are
|
|
63
|
+
clipped like water areas, cell by cell.
|
|
64
|
+
_Avoid_: boundary, extent, model area
|
|
65
|
+
|
|
66
|
+
**Refinement area**:
|
|
67
|
+
A polygon paired with the refinement level its cells must reach; transition
|
|
68
|
+
rows are added around it automatically.
|
|
69
|
+
_Avoid_: refinement polygon, refine zone, refinement (as a shape)
|
|
70
|
+
|
|
71
|
+
**Water area**:
|
|
72
|
+
An area where no 2D cells are kept, because the 1D network carries the flow there.
|
|
73
|
+
_Avoid_: water polygon, exclusion area, clip area
|
|
74
|
+
|
|
75
|
+
**Water removal rule**:
|
|
76
|
+
The test that decides whether a cell touching a water area (or a hole in the domain)
|
|
77
|
+
is removed from the finished mesh: by its circumcentre, only when fully inside, or as
|
|
78
|
+
soon as it touches; applied cell by cell with MeshKernel, as in the model workflow, so
|
|
79
|
+
the remaining small cells follow the water's edge.
|
|
80
|
+
_Avoid_: delete option, clip method
|